@voltagent/libsql 1.0.0-next.0 → 1.0.0-next.1
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.d.mts +338 -387
- package/dist/index.d.ts +338 -387
- package/dist/index.js +1766 -2675
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1754 -2671
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
9
|
var __export = (target, all) => {
|
|
@@ -16,3006 +18,2095 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
18
|
}
|
|
17
19
|
return to;
|
|
18
20
|
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
19
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
30
|
|
|
21
31
|
// src/index.ts
|
|
22
32
|
var index_exports = {};
|
|
23
33
|
__export(index_exports, {
|
|
24
|
-
|
|
34
|
+
LibSQLMemoryAdapter: () => LibSQLMemoryAdapter,
|
|
35
|
+
LibSQLObservabilityAdapter: () => LibSQLObservabilityAdapter,
|
|
36
|
+
LibSQLVectorAdapter: () => LibSQLVectorAdapter
|
|
25
37
|
});
|
|
26
38
|
module.exports = __toCommonJS(index_exports);
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
|
|
40
|
+
// src/memory-v2-adapter.ts
|
|
41
|
+
var import_node_fs = __toESM(require("fs"));
|
|
42
|
+
var import_node_path = __toESM(require("path"));
|
|
29
43
|
var import_client = require("@libsql/client");
|
|
30
44
|
var import_core = require("@voltagent/core");
|
|
31
|
-
var
|
|
32
|
-
var
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
var import_logger = require("@voltagent/logger");
|
|
46
|
+
var LibSQLMemoryAdapter = class {
|
|
47
|
+
static {
|
|
48
|
+
__name(this, "LibSQLMemoryAdapter");
|
|
49
|
+
}
|
|
50
|
+
client;
|
|
51
|
+
storageLimit;
|
|
52
|
+
tablePrefix;
|
|
53
|
+
initialized = false;
|
|
54
|
+
logger;
|
|
55
|
+
maxRetries;
|
|
56
|
+
retryDelayMs;
|
|
57
|
+
url;
|
|
58
|
+
constructor(options = {}) {
|
|
59
|
+
this.storageLimit = options.storageLimit ?? 100;
|
|
60
|
+
this.tablePrefix = options.tablePrefix ?? "voltagent_memory";
|
|
61
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
62
|
+
this.retryDelayMs = options.retryDelayMs ?? 100;
|
|
63
|
+
this.logger = options.logger || import_core.AgentRegistry.getInstance().getGlobalLogger() || (0, import_logger.createPinoLogger)({ name: "libsql-memory" });
|
|
64
|
+
this.url = options.url ?? "file:./.voltagent/memory.db";
|
|
65
|
+
if (this.url.startsWith("file:")) {
|
|
66
|
+
const dbPath = this.url.replace("file:", "");
|
|
67
|
+
const dbDir = import_node_path.default.dirname(dbPath);
|
|
68
|
+
if (dbDir && dbDir !== "." && !import_node_fs.default.existsSync(dbDir)) {
|
|
69
|
+
import_node_fs.default.mkdirSync(dbDir, { recursive: true });
|
|
70
|
+
this.logger.debug(`Created database directory: ${dbDir}`);
|
|
71
|
+
}
|
|
56
72
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.error(`[Migration] Failed to apply '${migrationName}':`, error);
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
__name(addSuspendedStatusMigration, "addSuspendedStatusMigration");
|
|
67
|
-
async function checkIfSuspendedStatusNeeded(db, tablePrefix) {
|
|
68
|
-
try {
|
|
69
|
-
const testId = `test-suspended-check-${Date.now()}`;
|
|
70
|
-
await db.execute({
|
|
71
|
-
sql: `
|
|
72
|
-
INSERT INTO ${tablePrefix}_workflow_history
|
|
73
|
-
(id, name, workflow_id, status, start_time)
|
|
74
|
-
VALUES (?, 'test', 'test', 'suspended', datetime('now'))
|
|
75
|
-
`,
|
|
76
|
-
args: [testId]
|
|
77
|
-
});
|
|
78
|
-
await db.execute({
|
|
79
|
-
sql: `DELETE FROM ${tablePrefix}_workflow_history WHERE id = ?`,
|
|
80
|
-
args: [testId]
|
|
73
|
+
this.client = (0, import_client.createClient)({
|
|
74
|
+
url: this.url,
|
|
75
|
+
authToken: options.authToken
|
|
81
76
|
});
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
this.logger.debug("LibSQL Memory adapter initialized", { url: this.url });
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Execute a database operation with retry logic
|
|
81
|
+
*/
|
|
82
|
+
async executeWithRetry(operation, operationName) {
|
|
83
|
+
let lastError;
|
|
84
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
85
|
+
try {
|
|
86
|
+
return await operation();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
lastError = error;
|
|
89
|
+
if (error?.code === "SQLITE_BUSY" || error?.message?.includes("SQLITE_BUSY") || error?.message?.includes("database is locked")) {
|
|
90
|
+
const delay = this.retryDelayMs * 2 ** attempt;
|
|
91
|
+
this.logger.debug(
|
|
92
|
+
`Database busy, retrying ${operationName} (attempt ${attempt + 1}/${this.maxRetries}) after ${delay}ms`
|
|
93
|
+
);
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
95
|
+
} else {
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
this.logger.error(
|
|
101
|
+
`Failed to execute ${operationName} after ${this.maxRetries} attempts`,
|
|
102
|
+
lastError
|
|
103
|
+
);
|
|
104
|
+
throw lastError;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Initialize database schema
|
|
108
|
+
*/
|
|
109
|
+
async initialize() {
|
|
110
|
+
if (this.initialized) return;
|
|
111
|
+
const conversationsTable = `${this.tablePrefix}_conversations`;
|
|
112
|
+
const messagesTable = `${this.tablePrefix}_messages`;
|
|
113
|
+
const usersTable = `${this.tablePrefix}_users`;
|
|
114
|
+
const workflowStatesTable = `${this.tablePrefix}_workflow_states`;
|
|
115
|
+
const isMemoryDb = this.url === ":memory:" || this.url.includes("mode=memory");
|
|
116
|
+
if (!isMemoryDb && (this.url.startsWith("file:") || this.url.startsWith("libsql:"))) {
|
|
117
|
+
try {
|
|
118
|
+
await this.client.execute("PRAGMA journal_mode=WAL");
|
|
119
|
+
this.logger.debug("Set PRAGMA journal_mode=WAL");
|
|
120
|
+
} catch (err) {
|
|
121
|
+
this.logger.debug("Failed to set PRAGMA journal_mode=WAL (non-critical)", { err });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
await this.client.execute("PRAGMA busy_timeout=5000");
|
|
126
|
+
this.logger.debug("Set PRAGMA busy_timeout=5000");
|
|
127
|
+
} catch (err) {
|
|
128
|
+
this.logger.debug("Failed to set PRAGMA busy_timeout (non-critical)", { err });
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
await this.client.execute("PRAGMA foreign_keys=ON");
|
|
132
|
+
this.logger.debug("Set PRAGMA foreign_keys=ON");
|
|
133
|
+
} catch (err) {
|
|
134
|
+
this.logger.debug("Failed to set PRAGMA foreign_keys (non-critical)", { err });
|
|
135
|
+
}
|
|
136
|
+
this.logger.debug("Applied PRAGMA settings for better concurrency");
|
|
137
|
+
await this.executeWithRetry(async () => {
|
|
138
|
+
await this.client.batch([
|
|
139
|
+
// Create users table (for user-level working memory)
|
|
140
|
+
`CREATE TABLE IF NOT EXISTS ${usersTable} (
|
|
141
|
+
id TEXT PRIMARY KEY,
|
|
142
|
+
metadata TEXT,
|
|
143
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
144
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
145
|
+
)`,
|
|
146
|
+
// Create conversations table (matching existing structure)
|
|
147
|
+
`CREATE TABLE IF NOT EXISTS ${conversationsTable} (
|
|
148
|
+
id TEXT PRIMARY KEY,
|
|
149
|
+
resource_id TEXT NOT NULL,
|
|
150
|
+
user_id TEXT NOT NULL,
|
|
151
|
+
title TEXT NOT NULL,
|
|
152
|
+
metadata TEXT NOT NULL,
|
|
153
|
+
created_at TEXT NOT NULL,
|
|
154
|
+
updated_at TEXT NOT NULL
|
|
155
|
+
)`,
|
|
156
|
+
// Create messages table (matching existing structure)
|
|
157
|
+
`CREATE TABLE IF NOT EXISTS ${messagesTable} (
|
|
158
|
+
conversation_id TEXT NOT NULL,
|
|
159
|
+
message_id TEXT NOT NULL,
|
|
160
|
+
user_id TEXT NOT NULL,
|
|
161
|
+
role TEXT NOT NULL,
|
|
162
|
+
parts TEXT NOT NULL,
|
|
163
|
+
metadata TEXT,
|
|
164
|
+
format_version INTEGER DEFAULT 2,
|
|
165
|
+
created_at TEXT NOT NULL,
|
|
166
|
+
PRIMARY KEY (conversation_id, message_id)
|
|
167
|
+
)`,
|
|
168
|
+
// Create workflow states table
|
|
169
|
+
`CREATE TABLE IF NOT EXISTS ${workflowStatesTable} (
|
|
96
170
|
id TEXT PRIMARY KEY,
|
|
97
|
-
name TEXT NOT NULL,
|
|
98
171
|
workflow_id TEXT NOT NULL,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
input TEXT,
|
|
103
|
-
output TEXT,
|
|
172
|
+
workflow_name TEXT NOT NULL,
|
|
173
|
+
status TEXT NOT NULL,
|
|
174
|
+
suspension TEXT,
|
|
104
175
|
user_id TEXT,
|
|
105
176
|
conversation_id TEXT,
|
|
106
177
|
metadata TEXT,
|
|
107
|
-
created_at TEXT
|
|
108
|
-
updated_at TEXT
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
await
|
|
121
|
-
|
|
122
|
-
);
|
|
123
|
-
await db.execute(
|
|
124
|
-
`CREATE INDEX idx_${tablePrefix}_workflow_history_status ON ${tablePrefix}_workflow_history(status)`
|
|
125
|
-
);
|
|
126
|
-
await db.execute(
|
|
127
|
-
`CREATE INDEX idx_${tablePrefix}_workflow_history_start_time ON ${tablePrefix}_workflow_history(start_time)`
|
|
128
|
-
);
|
|
129
|
-
await db.execute(
|
|
130
|
-
`CREATE INDEX idx_${tablePrefix}_workflow_history_user_id ON ${tablePrefix}_workflow_history(user_id)`
|
|
131
|
-
);
|
|
132
|
-
await db.execute(
|
|
133
|
-
`CREATE INDEX idx_${tablePrefix}_workflow_history_conversation_id ON ${tablePrefix}_workflow_history(conversation_id)`
|
|
134
|
-
);
|
|
135
|
-
await db.execute("COMMIT");
|
|
136
|
-
} catch (error) {
|
|
137
|
-
await db.execute("ROLLBACK");
|
|
138
|
-
throw error;
|
|
178
|
+
created_at TEXT NOT NULL,
|
|
179
|
+
updated_at TEXT NOT NULL
|
|
180
|
+
)`,
|
|
181
|
+
// Create indexes for better performance
|
|
182
|
+
`CREATE INDEX IF NOT EXISTS idx_${conversationsTable}_user_id ON ${conversationsTable}(user_id)`,
|
|
183
|
+
`CREATE INDEX IF NOT EXISTS idx_${conversationsTable}_resource_id ON ${conversationsTable}(resource_id)`,
|
|
184
|
+
`CREATE INDEX IF NOT EXISTS idx_${messagesTable}_conversation_id ON ${messagesTable}(conversation_id)`,
|
|
185
|
+
`CREATE INDEX IF NOT EXISTS idx_${messagesTable}_created_at ON ${messagesTable}(created_at)`,
|
|
186
|
+
`CREATE INDEX IF NOT EXISTS idx_${workflowStatesTable}_workflow_id ON ${workflowStatesTable}(workflow_id)`,
|
|
187
|
+
`CREATE INDEX IF NOT EXISTS idx_${workflowStatesTable}_status ON ${workflowStatesTable}(status)`
|
|
188
|
+
]);
|
|
189
|
+
}, "initialize database schema");
|
|
190
|
+
await this.addV2ColumnsToMessagesTable();
|
|
191
|
+
await this.migrateDefaultUserIds();
|
|
192
|
+
this.initialized = true;
|
|
193
|
+
this.logger.debug("Database schema initialized");
|
|
139
194
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
async
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
event_id TEXT NOT NULL,
|
|
216
|
-
name TEXT NOT NULL,
|
|
217
|
-
type TEXT NOT NULL CHECK (type IN ('workflow', 'workflow-step')),
|
|
218
|
-
start_time TEXT NOT NULL,
|
|
219
|
-
end_time TEXT,
|
|
220
|
-
status TEXT NOT NULL,
|
|
221
|
-
level TEXT DEFAULT 'INFO',
|
|
222
|
-
input TEXT,
|
|
223
|
-
output TEXT,
|
|
224
|
-
status_message TEXT,
|
|
225
|
-
metadata TEXT,
|
|
226
|
-
trace_id TEXT,
|
|
227
|
-
parent_event_id TEXT,
|
|
228
|
-
event_sequence INTEGER,
|
|
229
|
-
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
230
|
-
)
|
|
231
|
-
`);
|
|
232
|
-
await db.execute(
|
|
233
|
-
`CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_workflow_timeline_events_workflow_history ON ${tablePrefix}_workflow_timeline_events(workflow_history_id)`
|
|
234
|
-
);
|
|
235
|
-
await db.execute(
|
|
236
|
-
`CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_workflow_timeline_events_trace ON ${tablePrefix}_workflow_timeline_events(trace_id)`
|
|
237
|
-
);
|
|
238
|
-
await db.execute(
|
|
239
|
-
`CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_workflow_timeline_events_parent ON ${tablePrefix}_workflow_timeline_events(parent_event_id)`
|
|
240
|
-
);
|
|
241
|
-
await db.execute(
|
|
242
|
-
`CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_workflow_timeline_events_type ON ${tablePrefix}_workflow_timeline_events(type)`
|
|
243
|
-
);
|
|
244
|
-
await db.execute(
|
|
245
|
-
`CREATE INDEX IF NOT EXISTS idx_${tablePrefix}_workflow_timeline_events_sequence ON ${tablePrefix}_workflow_timeline_events(event_sequence)`
|
|
246
|
-
);
|
|
247
|
-
const checkWorkflowIdColumn = await db.execute(`
|
|
248
|
-
SELECT COUNT(*) as count
|
|
249
|
-
FROM pragma_table_info('agent_history')
|
|
250
|
-
WHERE name = 'workflow_id'
|
|
251
|
-
`);
|
|
252
|
-
if (checkWorkflowIdColumn.rows[0].count === 0) {
|
|
253
|
-
await db.execute("ALTER TABLE agent_history ADD COLUMN workflow_id TEXT");
|
|
254
|
-
}
|
|
255
|
-
const checkWorkflowStepIdColumn = await db.execute(`
|
|
256
|
-
SELECT COUNT(*) as count
|
|
257
|
-
FROM pragma_table_info('agent_history')
|
|
258
|
-
WHERE name = 'workflow_step_id'
|
|
259
|
-
`);
|
|
260
|
-
if (checkWorkflowStepIdColumn.rows[0].count === 0) {
|
|
261
|
-
await db.execute("ALTER TABLE agent_history ADD COLUMN workflow_step_id TEXT");
|
|
262
|
-
}
|
|
263
|
-
await db.execute(
|
|
264
|
-
"CREATE INDEX IF NOT EXISTS idx_agent_history_workflow_id ON agent_history(workflow_id)"
|
|
265
|
-
);
|
|
266
|
-
await db.execute(
|
|
267
|
-
"CREATE INDEX IF NOT EXISTS idx_agent_history_workflow_step ON agent_history(workflow_step_id)"
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
__name(createWorkflowTables, "createWorkflowTables");
|
|
271
|
-
|
|
272
|
-
// src/workflow-extension.ts
|
|
273
|
-
var import_utils = require("@voltagent/internal/utils");
|
|
274
|
-
var import_logger = require("@voltagent/logger");
|
|
275
|
-
var LibSQLWorkflowExtension = class {
|
|
276
|
-
constructor(client, _tablePrefix = "voltagent_memory", logger) {
|
|
277
|
-
this.client = client;
|
|
278
|
-
this._tablePrefix = _tablePrefix;
|
|
279
|
-
this.logger = logger || (0, import_logger.createPinoLogger)({ name: "libsql-workflow" });
|
|
195
|
+
/**
|
|
196
|
+
* Add new columns to messages table for V2 format if they don't exist
|
|
197
|
+
* This allows existing tables to support both old and new message formats
|
|
198
|
+
*/
|
|
199
|
+
async addV2ColumnsToMessagesTable() {
|
|
200
|
+
const messagesTableName = `${this.tablePrefix}_messages`;
|
|
201
|
+
try {
|
|
202
|
+
const tableInfo = await this.client.execute(`PRAGMA table_info(${messagesTableName})`);
|
|
203
|
+
const columns = tableInfo.rows.map((row) => row.name);
|
|
204
|
+
if (!columns.includes("parts")) {
|
|
205
|
+
try {
|
|
206
|
+
await this.client.execute(`ALTER TABLE ${messagesTableName} ADD COLUMN parts TEXT`);
|
|
207
|
+
} catch (_e) {
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (!columns.includes("metadata")) {
|
|
211
|
+
try {
|
|
212
|
+
await this.client.execute(`ALTER TABLE ${messagesTableName} ADD COLUMN metadata TEXT`);
|
|
213
|
+
} catch (_e) {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (!columns.includes("format_version")) {
|
|
217
|
+
try {
|
|
218
|
+
await this.client.execute(
|
|
219
|
+
`ALTER TABLE ${messagesTableName} ADD COLUMN format_version INTEGER DEFAULT 2`
|
|
220
|
+
);
|
|
221
|
+
} catch (_e) {
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!columns.includes("user_id")) {
|
|
225
|
+
try {
|
|
226
|
+
await this.client.execute(
|
|
227
|
+
`ALTER TABLE ${messagesTableName} ADD COLUMN user_id TEXT NOT NULL DEFAULT 'default'`
|
|
228
|
+
);
|
|
229
|
+
} catch (_e) {
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const contentInfo = tableInfo.rows.find((row) => row.name === "content");
|
|
233
|
+
if (contentInfo && contentInfo.notnull === 1) {
|
|
234
|
+
try {
|
|
235
|
+
await this.client.execute(
|
|
236
|
+
`ALTER TABLE ${messagesTableName} ADD COLUMN content_temp TEXT`
|
|
237
|
+
);
|
|
238
|
+
await this.client.execute(
|
|
239
|
+
`UPDATE ${messagesTableName} SET content_temp = content WHERE content IS NOT NULL`
|
|
240
|
+
);
|
|
241
|
+
try {
|
|
242
|
+
await this.client.execute(`ALTER TABLE ${messagesTableName} DROP COLUMN content`);
|
|
243
|
+
await this.client.execute(
|
|
244
|
+
`ALTER TABLE ${messagesTableName} RENAME COLUMN content_temp TO content`
|
|
245
|
+
);
|
|
246
|
+
} catch (_) {
|
|
247
|
+
}
|
|
248
|
+
} catch (_) {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const typeInfo = tableInfo.rows.find((row) => row.name === "type");
|
|
252
|
+
if (typeInfo && typeInfo.notnull === 1) {
|
|
253
|
+
try {
|
|
254
|
+
await this.client.execute(`ALTER TABLE ${messagesTableName} ADD COLUMN type_temp TEXT`);
|
|
255
|
+
await this.client.execute(
|
|
256
|
+
`UPDATE ${messagesTableName} SET type_temp = type WHERE type IS NOT NULL`
|
|
257
|
+
);
|
|
258
|
+
try {
|
|
259
|
+
await this.client.execute(`ALTER TABLE ${messagesTableName} DROP COLUMN type`);
|
|
260
|
+
await this.client.execute(
|
|
261
|
+
`ALTER TABLE ${messagesTableName} RENAME COLUMN type_temp TO type`
|
|
262
|
+
);
|
|
263
|
+
} catch (_) {
|
|
264
|
+
}
|
|
265
|
+
} catch (_) {
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} catch (_) {
|
|
269
|
+
}
|
|
280
270
|
}
|
|
281
|
-
|
|
282
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Migrate default user_id values in messages table
|
|
273
|
+
* Updates messages with user_id='default' to use the actual user_id from their conversation
|
|
274
|
+
*/
|
|
275
|
+
async migrateDefaultUserIds() {
|
|
276
|
+
const messagesTableName = `${this.tablePrefix}_messages`;
|
|
277
|
+
const conversationsTableName = `${this.tablePrefix}_conversations`;
|
|
278
|
+
try {
|
|
279
|
+
const checkResult = await this.client.execute({
|
|
280
|
+
sql: `SELECT COUNT(*) as count FROM ${messagesTableName} WHERE user_id = 'default'`,
|
|
281
|
+
args: []
|
|
282
|
+
});
|
|
283
|
+
const defaultCount = checkResult.rows[0]?.count || 0;
|
|
284
|
+
if (defaultCount === 0) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
this.logger.debug(`Found ${defaultCount} messages with default user_id, starting migration`);
|
|
288
|
+
await this.executeWithRetry(async () => {
|
|
289
|
+
const result = await this.client.execute({
|
|
290
|
+
sql: `UPDATE ${messagesTableName}
|
|
291
|
+
SET user_id = (
|
|
292
|
+
SELECT c.user_id
|
|
293
|
+
FROM ${conversationsTableName} c
|
|
294
|
+
WHERE c.id = ${messagesTableName}.conversation_id
|
|
295
|
+
)
|
|
296
|
+
WHERE user_id = 'default'
|
|
297
|
+
AND EXISTS (
|
|
298
|
+
SELECT 1
|
|
299
|
+
FROM ${conversationsTableName} c
|
|
300
|
+
WHERE c.id = ${messagesTableName}.conversation_id
|
|
301
|
+
)`,
|
|
302
|
+
args: []
|
|
303
|
+
});
|
|
304
|
+
const updatedCount = result.rowsAffected || 0;
|
|
305
|
+
this.logger.info(
|
|
306
|
+
`Successfully migrated ${updatedCount} messages from default user_id to actual user_ids`
|
|
307
|
+
);
|
|
308
|
+
const remainingResult = await this.client.execute({
|
|
309
|
+
sql: `SELECT COUNT(*) as count FROM ${messagesTableName} WHERE user_id = 'default'`,
|
|
310
|
+
args: []
|
|
311
|
+
});
|
|
312
|
+
const remainingCount = remainingResult.rows[0]?.count || 0;
|
|
313
|
+
if (remainingCount > 0) {
|
|
314
|
+
this.logger.warn(
|
|
315
|
+
`${remainingCount} messages still have default user_id (possibly orphaned messages without valid conversations)`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}, "migrate default user_ids");
|
|
319
|
+
} catch (error) {
|
|
320
|
+
this.logger.error("Failed to migrate default user_ids", error);
|
|
321
|
+
}
|
|
283
322
|
}
|
|
284
|
-
|
|
323
|
+
// ============================================================================
|
|
324
|
+
// Message Operations
|
|
325
|
+
// ============================================================================
|
|
285
326
|
/**
|
|
286
|
-
*
|
|
327
|
+
* Add a single message
|
|
287
328
|
*/
|
|
288
|
-
async
|
|
289
|
-
await this.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
});
|
|
329
|
+
async addMessage(message, userId, conversationId) {
|
|
330
|
+
await this.initialize();
|
|
331
|
+
const messagesTable = `${this.tablePrefix}_messages`;
|
|
332
|
+
const conversation = await this.getConversation(conversationId);
|
|
333
|
+
if (!conversation) {
|
|
334
|
+
throw new import_core.ConversationNotFoundError(conversationId);
|
|
335
|
+
}
|
|
336
|
+
await this.executeWithRetry(async () => {
|
|
337
|
+
await this.client.execute({
|
|
338
|
+
sql: `INSERT INTO ${messagesTable} (conversation_id, message_id, user_id, role, parts, metadata, format_version, created_at)
|
|
339
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
340
|
+
args: [
|
|
341
|
+
conversationId,
|
|
342
|
+
message.id,
|
|
343
|
+
userId,
|
|
344
|
+
message.role,
|
|
345
|
+
JSON.stringify(message.parts),
|
|
346
|
+
message.metadata ? JSON.stringify(message.metadata) : null,
|
|
347
|
+
2,
|
|
348
|
+
// format_version
|
|
349
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
350
|
+
]
|
|
351
|
+
});
|
|
352
|
+
}, "add message");
|
|
353
|
+
await this.applyStorageLimit(conversationId);
|
|
312
354
|
}
|
|
313
355
|
/**
|
|
314
|
-
*
|
|
356
|
+
* Add multiple messages
|
|
315
357
|
*/
|
|
316
|
-
async
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
358
|
+
async addMessages(messages, userId, conversationId) {
|
|
359
|
+
await this.initialize();
|
|
360
|
+
const messagesTable = `${this.tablePrefix}_messages`;
|
|
361
|
+
const conversation = await this.getConversation(conversationId);
|
|
362
|
+
if (!conversation) {
|
|
363
|
+
throw new import_core.ConversationNotFoundError(conversationId);
|
|
364
|
+
}
|
|
365
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
366
|
+
await this.executeWithRetry(async () => {
|
|
367
|
+
await this.client.batch(
|
|
368
|
+
messages.map((message) => ({
|
|
369
|
+
sql: `INSERT INTO ${messagesTable} (conversation_id, message_id, user_id, role, parts, metadata, format_version, created_at)
|
|
370
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
371
|
+
args: [
|
|
372
|
+
conversationId,
|
|
373
|
+
message.id,
|
|
374
|
+
userId,
|
|
375
|
+
message.role,
|
|
376
|
+
JSON.stringify(message.parts),
|
|
377
|
+
message.metadata ? JSON.stringify(message.metadata) : null,
|
|
378
|
+
2,
|
|
379
|
+
// format_version
|
|
380
|
+
now
|
|
381
|
+
]
|
|
382
|
+
}))
|
|
383
|
+
);
|
|
384
|
+
}, "add batch messages");
|
|
385
|
+
await this.applyStorageLimit(conversationId);
|
|
323
386
|
}
|
|
324
387
|
/**
|
|
325
|
-
*
|
|
388
|
+
* Apply storage limit to a conversation
|
|
326
389
|
*/
|
|
327
|
-
async
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
390
|
+
async applyStorageLimit(conversationId) {
|
|
391
|
+
const messagesTable = `${this.tablePrefix}_messages`;
|
|
392
|
+
await this.executeWithRetry(async () => {
|
|
393
|
+
await this.client.execute({
|
|
394
|
+
sql: `DELETE FROM ${messagesTable}
|
|
395
|
+
WHERE conversation_id = ?
|
|
396
|
+
AND message_id NOT IN (
|
|
397
|
+
SELECT message_id FROM ${messagesTable}
|
|
398
|
+
WHERE conversation_id = ?
|
|
399
|
+
ORDER BY created_at DESC
|
|
400
|
+
LIMIT ?
|
|
401
|
+
)`,
|
|
402
|
+
args: [conversationId, conversationId, this.storageLimit]
|
|
403
|
+
});
|
|
404
|
+
}, "apply storage limit");
|
|
333
405
|
}
|
|
334
406
|
/**
|
|
335
|
-
*
|
|
407
|
+
* Get messages with optional filtering
|
|
336
408
|
*/
|
|
337
|
-
async
|
|
338
|
-
this.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
args.push(
|
|
348
|
-
}
|
|
349
|
-
if (updates.endTime !== void 0) {
|
|
350
|
-
setClauses.push("end_time = ?");
|
|
351
|
-
args.push(updates.endTime.toISOString());
|
|
409
|
+
async getMessages(userId, conversationId, options) {
|
|
410
|
+
await this.initialize();
|
|
411
|
+
const messagesTable = `${this.tablePrefix}_messages`;
|
|
412
|
+
const { limit = this.storageLimit, before, after, roles } = options || {};
|
|
413
|
+
let sql = `SELECT * FROM ${messagesTable}
|
|
414
|
+
WHERE conversation_id = ? AND user_id = ?`;
|
|
415
|
+
const args = [conversationId, userId];
|
|
416
|
+
if (roles && roles.length > 0) {
|
|
417
|
+
const placeholders = roles.map(() => "?").join(",");
|
|
418
|
+
sql += ` AND role IN (${placeholders})`;
|
|
419
|
+
args.push(...roles);
|
|
352
420
|
}
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
args.push(
|
|
421
|
+
if (before) {
|
|
422
|
+
sql += " AND created_at < ?";
|
|
423
|
+
args.push(before.toISOString());
|
|
356
424
|
}
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
args.push(
|
|
425
|
+
if (after) {
|
|
426
|
+
sql += " AND created_at > ?";
|
|
427
|
+
args.push(after.toISOString());
|
|
360
428
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if (updates.metadata !== void 0) {
|
|
366
|
-
setClauses.push("metadata = ?");
|
|
367
|
-
const metadataJson = (0, import_utils.safeStringify)(updates.metadata);
|
|
368
|
-
args.push(metadataJson);
|
|
369
|
-
this.logger.trace(`Setting metadata for ${id}:`, { metadata: metadataJson });
|
|
370
|
-
}
|
|
371
|
-
setClauses.push("updated_at = ?");
|
|
372
|
-
args.push((/* @__PURE__ */ new Date()).toISOString());
|
|
373
|
-
args.push(id);
|
|
374
|
-
const sql = `UPDATE ${this._tablePrefix}_workflow_history SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
375
|
-
this.logger.trace("Executing SQL:", { sql, args });
|
|
376
|
-
try {
|
|
377
|
-
const result = await this.client.execute({ sql, args });
|
|
378
|
-
this.logger.trace(
|
|
379
|
-
`Successfully updated workflow history ${id}, rows affected: ${result.rowsAffected}`
|
|
380
|
-
);
|
|
381
|
-
} catch (error) {
|
|
382
|
-
this.logger.error(`Failed to update workflow history ${id}:`, { error });
|
|
383
|
-
throw error;
|
|
429
|
+
sql += " ORDER BY created_at ASC";
|
|
430
|
+
if (limit && limit > 0) {
|
|
431
|
+
sql += " LIMIT ?";
|
|
432
|
+
args.push(limit);
|
|
384
433
|
}
|
|
434
|
+
const result = await this.client.execute({ sql, args });
|
|
435
|
+
return result.rows.map((row) => {
|
|
436
|
+
let parts;
|
|
437
|
+
if (row.parts !== void 0 && row.parts !== null) {
|
|
438
|
+
try {
|
|
439
|
+
parts = JSON.parse(row.parts);
|
|
440
|
+
} catch {
|
|
441
|
+
parts = [];
|
|
442
|
+
}
|
|
443
|
+
} else if (row.content !== void 0 && row.content !== null) {
|
|
444
|
+
try {
|
|
445
|
+
const content = JSON.parse(row.content);
|
|
446
|
+
if (typeof content === "string") {
|
|
447
|
+
parts = [{ type: "text", text: content }];
|
|
448
|
+
} else if (Array.isArray(content)) {
|
|
449
|
+
parts = content;
|
|
450
|
+
} else {
|
|
451
|
+
parts = [];
|
|
452
|
+
}
|
|
453
|
+
} catch {
|
|
454
|
+
parts = [{ type: "text", text: row.content }];
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
parts = [];
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
id: row.message_id,
|
|
461
|
+
role: row.role,
|
|
462
|
+
parts,
|
|
463
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
464
|
+
};
|
|
465
|
+
});
|
|
385
466
|
}
|
|
386
467
|
/**
|
|
387
|
-
*
|
|
468
|
+
* Clear messages for a user
|
|
388
469
|
*/
|
|
389
|
-
async
|
|
390
|
-
await this.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
470
|
+
async clearMessages(userId, conversationId) {
|
|
471
|
+
await this.initialize();
|
|
472
|
+
const messagesTable = `${this.tablePrefix}_messages`;
|
|
473
|
+
const conversationsTable = `${this.tablePrefix}_conversations`;
|
|
474
|
+
if (conversationId) {
|
|
475
|
+
await this.client.execute({
|
|
476
|
+
sql: `DELETE FROM ${messagesTable} WHERE conversation_id = ? AND user_id = ?`,
|
|
477
|
+
args: [conversationId, userId]
|
|
478
|
+
});
|
|
479
|
+
} else {
|
|
480
|
+
await this.client.execute({
|
|
481
|
+
sql: `DELETE FROM ${messagesTable}
|
|
482
|
+
WHERE conversation_id IN (
|
|
483
|
+
SELECT id FROM ${conversationsTable} WHERE user_id = ?
|
|
484
|
+
)`,
|
|
485
|
+
args: [userId]
|
|
486
|
+
});
|
|
487
|
+
}
|
|
394
488
|
}
|
|
489
|
+
// ============================================================================
|
|
490
|
+
// Conversation Operations
|
|
491
|
+
// ============================================================================
|
|
395
492
|
/**
|
|
396
|
-
*
|
|
493
|
+
* Create a new conversation
|
|
397
494
|
*/
|
|
398
|
-
async
|
|
399
|
-
await this.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
495
|
+
async createConversation(input) {
|
|
496
|
+
await this.initialize();
|
|
497
|
+
const conversationsTable = `${this.tablePrefix}_conversations`;
|
|
498
|
+
const existing = await this.getConversation(input.id);
|
|
499
|
+
if (existing) {
|
|
500
|
+
throw new import_core.ConversationAlreadyExistsError(input.id);
|
|
501
|
+
}
|
|
502
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
503
|
+
await this.executeWithRetry(async () => {
|
|
504
|
+
await this.client.execute({
|
|
505
|
+
sql: `INSERT INTO ${conversationsTable} (id, resource_id, user_id, title, metadata, created_at, updated_at)
|
|
506
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
507
|
+
args: [
|
|
508
|
+
input.id,
|
|
509
|
+
input.resourceId,
|
|
510
|
+
input.userId,
|
|
511
|
+
input.title,
|
|
512
|
+
JSON.stringify(input.metadata || {}),
|
|
513
|
+
now,
|
|
514
|
+
now
|
|
515
|
+
]
|
|
516
|
+
});
|
|
517
|
+
}, "create conversation");
|
|
518
|
+
return {
|
|
519
|
+
id: input.id,
|
|
520
|
+
userId: input.userId,
|
|
521
|
+
resourceId: input.resourceId,
|
|
522
|
+
title: input.title,
|
|
523
|
+
metadata: input.metadata || {},
|
|
524
|
+
createdAt: now,
|
|
525
|
+
updatedAt: now
|
|
526
|
+
};
|
|
429
527
|
}
|
|
430
528
|
/**
|
|
431
|
-
* Get a
|
|
529
|
+
* Get a conversation by ID
|
|
432
530
|
*/
|
|
433
|
-
async
|
|
531
|
+
async getConversation(id) {
|
|
532
|
+
await this.initialize();
|
|
533
|
+
const conversationsTable = `${this.tablePrefix}_conversations`;
|
|
434
534
|
const result = await this.client.execute({
|
|
435
|
-
sql: `SELECT * FROM ${
|
|
535
|
+
sql: `SELECT * FROM ${conversationsTable} WHERE id = ?`,
|
|
436
536
|
args: [id]
|
|
437
537
|
});
|
|
438
|
-
if (result.rows.length === 0)
|
|
439
|
-
|
|
538
|
+
if (result.rows.length === 0) {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
const row = result.rows[0];
|
|
542
|
+
return {
|
|
543
|
+
id: row.id,
|
|
544
|
+
userId: row.user_id,
|
|
545
|
+
resourceId: row.resource_id,
|
|
546
|
+
title: row.title,
|
|
547
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {},
|
|
548
|
+
createdAt: row.created_at,
|
|
549
|
+
updatedAt: row.updated_at
|
|
550
|
+
};
|
|
440
551
|
}
|
|
441
552
|
/**
|
|
442
|
-
* Get
|
|
553
|
+
* Get conversations by resource ID
|
|
443
554
|
*/
|
|
444
|
-
async
|
|
555
|
+
async getConversations(resourceId) {
|
|
556
|
+
await this.initialize();
|
|
557
|
+
const conversationsTable = `${this.tablePrefix}_conversations`;
|
|
445
558
|
const result = await this.client.execute({
|
|
446
|
-
sql: `SELECT * FROM ${
|
|
447
|
-
args: [
|
|
559
|
+
sql: `SELECT * FROM ${conversationsTable} WHERE resource_id = ? ORDER BY updated_at DESC`,
|
|
560
|
+
args: [resourceId]
|
|
448
561
|
});
|
|
449
|
-
return result.rows.map((row) =>
|
|
562
|
+
return result.rows.map((row) => ({
|
|
563
|
+
id: row.id,
|
|
564
|
+
userId: row.user_id,
|
|
565
|
+
resourceId: row.resource_id,
|
|
566
|
+
title: row.title,
|
|
567
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {},
|
|
568
|
+
createdAt: row.created_at,
|
|
569
|
+
updatedAt: row.updated_at
|
|
570
|
+
}));
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Get conversations by user ID
|
|
574
|
+
*/
|
|
575
|
+
async getConversationsByUserId(userId, options) {
|
|
576
|
+
return this.queryConversations({ ...options, userId });
|
|
450
577
|
}
|
|
451
578
|
/**
|
|
452
|
-
*
|
|
579
|
+
* Query conversations with filters
|
|
453
580
|
*/
|
|
454
|
-
async
|
|
455
|
-
|
|
581
|
+
async queryConversations(options) {
|
|
582
|
+
await this.initialize();
|
|
583
|
+
const conversationsTable = `${this.tablePrefix}_conversations`;
|
|
584
|
+
let sql = `SELECT * FROM ${conversationsTable} WHERE 1=1`;
|
|
456
585
|
const args = [];
|
|
457
|
-
if (
|
|
458
|
-
|
|
459
|
-
args.push(
|
|
586
|
+
if (options.userId) {
|
|
587
|
+
sql += " AND user_id = ?";
|
|
588
|
+
args.push(options.userId);
|
|
589
|
+
}
|
|
590
|
+
if (options.resourceId) {
|
|
591
|
+
sql += " AND resource_id = ?";
|
|
592
|
+
args.push(options.resourceId);
|
|
460
593
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
594
|
+
const orderBy = options.orderBy || "updated_at";
|
|
595
|
+
const orderDirection = options.orderDirection || "DESC";
|
|
596
|
+
sql += ` ORDER BY ${orderBy} ${orderDirection}`;
|
|
597
|
+
if (options.limit) {
|
|
598
|
+
sql += " LIMIT ?";
|
|
599
|
+
args.push(options.limit);
|
|
464
600
|
}
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
args.push(
|
|
601
|
+
if (options.offset) {
|
|
602
|
+
sql += " OFFSET ?";
|
|
603
|
+
args.push(options.offset);
|
|
468
604
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
605
|
+
const result = await this.client.execute({ sql, args });
|
|
606
|
+
return result.rows.map((row) => ({
|
|
607
|
+
id: row.id,
|
|
608
|
+
userId: row.user_id,
|
|
609
|
+
resourceId: row.resource_id,
|
|
610
|
+
title: row.title,
|
|
611
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {},
|
|
612
|
+
createdAt: row.created_at,
|
|
613
|
+
updatedAt: row.updated_at
|
|
614
|
+
}));
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Update a conversation
|
|
618
|
+
*/
|
|
619
|
+
async updateConversation(id, updates) {
|
|
620
|
+
await this.initialize();
|
|
621
|
+
const conversationsTable = `${this.tablePrefix}_conversations`;
|
|
622
|
+
const conversation = await this.getConversation(id);
|
|
623
|
+
if (!conversation) {
|
|
624
|
+
throw new import_core.ConversationNotFoundError(id);
|
|
625
|
+
}
|
|
626
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
627
|
+
const fieldsToUpdate = ["updated_at = ?"];
|
|
628
|
+
const args = [now];
|
|
629
|
+
if (updates.title !== void 0) {
|
|
630
|
+
fieldsToUpdate.push("title = ?");
|
|
631
|
+
args.push(updates.title);
|
|
472
632
|
}
|
|
473
|
-
if (updates.
|
|
474
|
-
|
|
475
|
-
args.push(updates.
|
|
633
|
+
if (updates.resourceId !== void 0) {
|
|
634
|
+
fieldsToUpdate.push("resource_id = ?");
|
|
635
|
+
args.push(updates.resourceId);
|
|
476
636
|
}
|
|
477
637
|
if (updates.metadata !== void 0) {
|
|
478
|
-
|
|
479
|
-
args.push(
|
|
638
|
+
fieldsToUpdate.push("metadata = ?");
|
|
639
|
+
args.push(JSON.stringify(updates.metadata));
|
|
480
640
|
}
|
|
481
|
-
setClauses.push("updated_at = ?");
|
|
482
|
-
args.push((/* @__PURE__ */ new Date()).toISOString());
|
|
483
641
|
args.push(id);
|
|
484
642
|
await this.client.execute({
|
|
485
|
-
sql: `UPDATE ${
|
|
643
|
+
sql: `UPDATE ${conversationsTable} SET ${fieldsToUpdate.join(", ")} WHERE id = ?`,
|
|
486
644
|
args
|
|
487
645
|
});
|
|
646
|
+
const updated = await this.getConversation(id);
|
|
647
|
+
if (!updated) {
|
|
648
|
+
throw new Error(`Conversation not found after update: ${id}`);
|
|
649
|
+
}
|
|
650
|
+
return updated;
|
|
488
651
|
}
|
|
489
652
|
/**
|
|
490
|
-
* Delete a
|
|
653
|
+
* Delete a conversation
|
|
491
654
|
*/
|
|
492
|
-
async
|
|
655
|
+
async deleteConversation(id) {
|
|
656
|
+
await this.initialize();
|
|
657
|
+
const conversationsTable = `${this.tablePrefix}_conversations`;
|
|
493
658
|
await this.client.execute({
|
|
494
|
-
sql: `DELETE FROM ${
|
|
659
|
+
sql: `DELETE FROM ${conversationsTable} WHERE id = ?`,
|
|
495
660
|
args: [id]
|
|
496
661
|
});
|
|
497
662
|
}
|
|
663
|
+
// ============================================================================
|
|
664
|
+
// Working Memory Operations
|
|
665
|
+
// ============================================================================
|
|
498
666
|
/**
|
|
499
|
-
*
|
|
667
|
+
* Get working memory
|
|
500
668
|
*/
|
|
501
|
-
async
|
|
502
|
-
await this.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
event.level || "INFO",
|
|
520
|
-
event.input ? (0, import_utils.safeStringify)(event.input) : null,
|
|
521
|
-
event.output ? (0, import_utils.safeStringify)(event.output) : null,
|
|
522
|
-
event.statusMessage ? (0, import_utils.safeStringify)(event.statusMessage) : null,
|
|
523
|
-
event.metadata ? (0, import_utils.safeStringify)(event.metadata) : null,
|
|
524
|
-
event.traceId || null,
|
|
525
|
-
event.parentEventId || null,
|
|
526
|
-
event.eventSequence || null,
|
|
527
|
-
// Event sequence for ordering
|
|
528
|
-
event.createdAt.toISOString()
|
|
529
|
-
]
|
|
530
|
-
});
|
|
669
|
+
async getWorkingMemory(params) {
|
|
670
|
+
await this.initialize();
|
|
671
|
+
if (params.scope === "conversation" && params.conversationId) {
|
|
672
|
+
const conversation = await this.getConversation(params.conversationId);
|
|
673
|
+
return conversation?.metadata?.workingMemory || null;
|
|
674
|
+
}
|
|
675
|
+
if (params.scope === "user" && params.userId) {
|
|
676
|
+
const usersTable = `${this.tablePrefix}_users`;
|
|
677
|
+
const result = await this.client.execute({
|
|
678
|
+
sql: `SELECT metadata FROM ${usersTable} WHERE id = ?`,
|
|
679
|
+
args: [params.userId]
|
|
680
|
+
});
|
|
681
|
+
if (result.rows.length > 0) {
|
|
682
|
+
const metadata = result.rows[0].metadata ? JSON.parse(result.rows[0].metadata) : {};
|
|
683
|
+
return metadata.workingMemory || null;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return null;
|
|
531
687
|
}
|
|
532
688
|
/**
|
|
533
|
-
*
|
|
689
|
+
* Set working memory
|
|
534
690
|
*/
|
|
535
|
-
async
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
691
|
+
async setWorkingMemory(params) {
|
|
692
|
+
await this.initialize();
|
|
693
|
+
if (params.scope === "conversation" && params.conversationId) {
|
|
694
|
+
const conversation = await this.getConversation(params.conversationId);
|
|
695
|
+
if (!conversation) {
|
|
696
|
+
throw new import_core.ConversationNotFoundError(params.conversationId);
|
|
697
|
+
}
|
|
698
|
+
const metadata = conversation.metadata || {};
|
|
699
|
+
metadata.workingMemory = params.content;
|
|
700
|
+
await this.updateConversation(params.conversationId, { metadata });
|
|
701
|
+
}
|
|
702
|
+
if (params.scope === "user" && params.userId) {
|
|
703
|
+
const usersTable = `${this.tablePrefix}_users`;
|
|
704
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
705
|
+
const result = await this.client.execute({
|
|
706
|
+
sql: `SELECT metadata FROM ${usersTable} WHERE id = ?`,
|
|
707
|
+
args: [params.userId]
|
|
708
|
+
});
|
|
709
|
+
if (result.rows.length > 0) {
|
|
710
|
+
const metadata = result.rows[0].metadata ? JSON.parse(result.rows[0].metadata) : {};
|
|
711
|
+
metadata.workingMemory = params.content;
|
|
712
|
+
await this.client.execute({
|
|
713
|
+
sql: `UPDATE ${usersTable} SET metadata = ?, updated_at = ? WHERE id = ?`,
|
|
714
|
+
args: [JSON.stringify(metadata), now, params.userId]
|
|
715
|
+
});
|
|
716
|
+
} else {
|
|
717
|
+
await this.client.execute({
|
|
718
|
+
sql: `INSERT INTO ${usersTable} (id, metadata, created_at, updated_at) VALUES (?, ?, ?, ?)`,
|
|
719
|
+
args: [params.userId, JSON.stringify({ workingMemory: params.content }), now, now]
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Delete working memory
|
|
726
|
+
*/
|
|
727
|
+
async deleteWorkingMemory(params) {
|
|
728
|
+
await this.initialize();
|
|
729
|
+
if (params.scope === "conversation" && params.conversationId) {
|
|
730
|
+
const conversation = await this.getConversation(params.conversationId);
|
|
731
|
+
if (conversation?.metadata?.workingMemory) {
|
|
732
|
+
const metadata = { ...conversation.metadata };
|
|
733
|
+
delete metadata.workingMemory;
|
|
734
|
+
await this.updateConversation(params.conversationId, { metadata });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (params.scope === "user" && params.userId) {
|
|
738
|
+
const usersTable = `${this.tablePrefix}_users`;
|
|
739
|
+
const result = await this.client.execute({
|
|
740
|
+
sql: `SELECT metadata FROM ${usersTable} WHERE id = ?`,
|
|
741
|
+
args: [params.userId]
|
|
742
|
+
});
|
|
743
|
+
if (result.rows.length > 0 && result.rows[0].metadata) {
|
|
744
|
+
const metadata = JSON.parse(result.rows[0].metadata);
|
|
745
|
+
if (metadata.workingMemory) {
|
|
746
|
+
delete metadata.workingMemory;
|
|
747
|
+
await this.client.execute({
|
|
748
|
+
sql: `UPDATE ${usersTable} SET metadata = ?, updated_at = ? WHERE id = ?`,
|
|
749
|
+
args: [JSON.stringify(metadata), (/* @__PURE__ */ new Date()).toISOString(), params.userId]
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
542
754
|
}
|
|
755
|
+
// ============================================================================
|
|
756
|
+
// Workflow State Operations
|
|
757
|
+
// ============================================================================
|
|
543
758
|
/**
|
|
544
|
-
* Get
|
|
759
|
+
* Get workflow state by execution ID
|
|
545
760
|
*/
|
|
546
|
-
async
|
|
761
|
+
async getWorkflowState(executionId) {
|
|
762
|
+
await this.initialize();
|
|
763
|
+
const workflowStatesTable = `${this.tablePrefix}_workflow_states`;
|
|
547
764
|
const result = await this.client.execute({
|
|
548
|
-
sql: `SELECT * FROM ${
|
|
549
|
-
args: [
|
|
765
|
+
sql: `SELECT * FROM ${workflowStatesTable} WHERE id = ?`,
|
|
766
|
+
args: [executionId]
|
|
550
767
|
});
|
|
551
|
-
|
|
768
|
+
if (result.rows.length === 0) {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
const row = result.rows[0];
|
|
772
|
+
return {
|
|
773
|
+
id: row.id,
|
|
774
|
+
workflowId: row.workflow_id,
|
|
775
|
+
workflowName: row.workflow_name,
|
|
776
|
+
status: row.status,
|
|
777
|
+
suspension: row.suspension ? JSON.parse(row.suspension) : void 0,
|
|
778
|
+
userId: row.user_id,
|
|
779
|
+
conversationId: row.conversation_id,
|
|
780
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
781
|
+
createdAt: new Date(row.created_at),
|
|
782
|
+
updatedAt: new Date(row.updated_at)
|
|
783
|
+
};
|
|
552
784
|
}
|
|
553
785
|
/**
|
|
554
|
-
*
|
|
786
|
+
* Set workflow state
|
|
555
787
|
*/
|
|
556
|
-
async
|
|
788
|
+
async setWorkflowState(executionId, state) {
|
|
789
|
+
await this.initialize();
|
|
790
|
+
const workflowStatesTable = `${this.tablePrefix}_workflow_states`;
|
|
557
791
|
await this.client.execute({
|
|
558
|
-
sql: `
|
|
559
|
-
|
|
792
|
+
sql: `INSERT OR REPLACE INTO ${workflowStatesTable}
|
|
793
|
+
(id, workflow_id, workflow_name, status, suspension, user_id, conversation_id, metadata, created_at, updated_at)
|
|
794
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
795
|
+
args: [
|
|
796
|
+
executionId,
|
|
797
|
+
state.workflowId,
|
|
798
|
+
state.workflowName,
|
|
799
|
+
state.status,
|
|
800
|
+
state.suspension ? JSON.stringify(state.suspension) : null,
|
|
801
|
+
state.userId || null,
|
|
802
|
+
state.conversationId || null,
|
|
803
|
+
state.metadata ? JSON.stringify(state.metadata) : null,
|
|
804
|
+
state.createdAt.toISOString(),
|
|
805
|
+
state.updatedAt.toISOString()
|
|
806
|
+
]
|
|
560
807
|
});
|
|
561
808
|
}
|
|
562
809
|
/**
|
|
563
|
-
*
|
|
810
|
+
* Update workflow state
|
|
564
811
|
*/
|
|
565
|
-
async
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
return result.rows.map((row) => row.workflow_id);
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Get workflow statistics
|
|
574
|
-
*/
|
|
575
|
-
async getWorkflowStats(workflowId) {
|
|
576
|
-
const result = await this.client.execute({
|
|
577
|
-
sql: `
|
|
578
|
-
SELECT
|
|
579
|
-
COUNT(*) as total_executions,
|
|
580
|
-
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as successful_executions,
|
|
581
|
-
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as failed_executions,
|
|
582
|
-
AVG(CASE WHEN end_time IS NOT NULL THEN
|
|
583
|
-
(julianday(end_time) - julianday(start_time)) * 24 * 60 * 60 * 1000
|
|
584
|
-
ELSE NULL END) as avg_duration_ms,
|
|
585
|
-
MAX(start_time) as last_execution_time
|
|
586
|
-
FROM ${this._tablePrefix}_workflow_history
|
|
587
|
-
WHERE workflow_id = ?
|
|
588
|
-
`,
|
|
589
|
-
args: [workflowId]
|
|
590
|
-
});
|
|
591
|
-
if (result.rows.length === 0) {
|
|
592
|
-
return {
|
|
593
|
-
totalExecutions: 0,
|
|
594
|
-
successfulExecutions: 0,
|
|
595
|
-
failedExecutions: 0,
|
|
596
|
-
averageExecutionTime: 0,
|
|
597
|
-
lastExecutionTime: void 0
|
|
598
|
-
};
|
|
812
|
+
async updateWorkflowState(executionId, updates) {
|
|
813
|
+
await this.initialize();
|
|
814
|
+
const existing = await this.getWorkflowState(executionId);
|
|
815
|
+
if (!existing) {
|
|
816
|
+
throw new Error(`Workflow state ${executionId} not found`);
|
|
599
817
|
}
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
failedExecutions: Number(row.failed_executions) || 0,
|
|
605
|
-
averageExecutionTime: Number(row.avg_duration_ms) || 0,
|
|
606
|
-
lastExecutionTime: row.last_execution_time ? new Date(row.last_execution_time) : void 0
|
|
818
|
+
const updated = {
|
|
819
|
+
...existing,
|
|
820
|
+
...updates,
|
|
821
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
607
822
|
};
|
|
823
|
+
await this.setWorkflowState(executionId, updated);
|
|
608
824
|
}
|
|
609
825
|
/**
|
|
610
|
-
* Get workflow
|
|
611
|
-
*/
|
|
612
|
-
async getWorkflowHistoryWithStepsAndEvents(id) {
|
|
613
|
-
const history = await this.getWorkflowHistory(id);
|
|
614
|
-
if (!history) return null;
|
|
615
|
-
const [steps, events] = await Promise.all([
|
|
616
|
-
this.getWorkflowSteps(id),
|
|
617
|
-
this.getWorkflowTimelineEvents(id)
|
|
618
|
-
]);
|
|
619
|
-
history.steps = steps;
|
|
620
|
-
history.events = events;
|
|
621
|
-
return history;
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Delete workflow history and all related data
|
|
625
|
-
*/
|
|
626
|
-
async deleteWorkflowHistoryWithRelated(id) {
|
|
627
|
-
await this.deleteWorkflowHistory(id);
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Clean up old workflow histories
|
|
826
|
+
* Get suspended workflow states for a workflow
|
|
631
827
|
*/
|
|
632
|
-
async
|
|
633
|
-
|
|
634
|
-
|
|
828
|
+
async getSuspendedWorkflowStates(workflowId) {
|
|
829
|
+
await this.initialize();
|
|
830
|
+
const workflowStatesTable = `${this.tablePrefix}_workflow_states`;
|
|
831
|
+
const result = await this.client.execute({
|
|
832
|
+
sql: `SELECT * FROM ${workflowStatesTable} WHERE workflow_id = ? AND status = 'suspended' ORDER BY created_at DESC`,
|
|
635
833
|
args: [workflowId]
|
|
636
834
|
});
|
|
637
|
-
|
|
638
|
-
if (currentCount <= maxEntries) return 0;
|
|
639
|
-
const deleteCount = currentCount - maxEntries;
|
|
640
|
-
const deleteResult = await this.client.execute({
|
|
641
|
-
sql: `
|
|
642
|
-
DELETE FROM ${this._tablePrefix}_workflow_history
|
|
643
|
-
WHERE workflow_id = ?
|
|
644
|
-
AND id IN (
|
|
645
|
-
SELECT id FROM ${this._tablePrefix}_workflow_history
|
|
646
|
-
WHERE workflow_id = ?
|
|
647
|
-
ORDER BY start_time ASC
|
|
648
|
-
LIMIT ?
|
|
649
|
-
)
|
|
650
|
-
`,
|
|
651
|
-
args: [workflowId, workflowId, deleteCount]
|
|
652
|
-
});
|
|
653
|
-
return deleteResult.rowsAffected;
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Parse workflow history row from database
|
|
657
|
-
*/
|
|
658
|
-
parseWorkflowHistoryRow(row) {
|
|
659
|
-
return {
|
|
835
|
+
return result.rows.map((row) => ({
|
|
660
836
|
id: row.id,
|
|
661
|
-
workflowName: row.name,
|
|
662
837
|
workflowId: row.workflow_id,
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
input: row.input ? JSON.parse(row.input) : null,
|
|
667
|
-
output: row.output ? JSON.parse(row.output) : void 0,
|
|
838
|
+
workflowName: row.workflow_name,
|
|
839
|
+
status: "suspended",
|
|
840
|
+
suspension: row.suspension ? JSON.parse(row.suspension) : void 0,
|
|
668
841
|
userId: row.user_id,
|
|
669
842
|
conversationId: row.conversation_id,
|
|
670
843
|
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
671
|
-
steps: [],
|
|
672
|
-
// Will be loaded separately if needed
|
|
673
|
-
events: [],
|
|
674
|
-
// Will be loaded separately if needed
|
|
675
|
-
createdAt: new Date(row.created_at),
|
|
676
|
-
updatedAt: new Date(row.updated_at)
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
/**
|
|
680
|
-
* Parse workflow step row from database
|
|
681
|
-
*/
|
|
682
|
-
parseWorkflowStepRow(row) {
|
|
683
|
-
return {
|
|
684
|
-
id: row.id,
|
|
685
|
-
workflowHistoryId: row.workflow_history_id,
|
|
686
|
-
stepIndex: Number(row.step_index),
|
|
687
|
-
stepType: row.step_type,
|
|
688
|
-
stepName: row.step_name,
|
|
689
|
-
stepId: row.step_id || void 0,
|
|
690
|
-
status: row.status,
|
|
691
|
-
startTime: new Date(row.start_time),
|
|
692
|
-
endTime: row.end_time ? new Date(row.end_time) : void 0,
|
|
693
|
-
input: row.input ? JSON.parse(row.input) : void 0,
|
|
694
|
-
output: row.output ? JSON.parse(row.output) : void 0,
|
|
695
|
-
error: row.error_message ? JSON.parse(row.error_message) : void 0,
|
|
696
|
-
agentExecutionId: row.agent_execution_id || void 0,
|
|
697
|
-
parallelIndex: row.parallel_index ? Number(row.parallel_index) : void 0,
|
|
698
|
-
parallelParentStepId: row.parent_step_id || void 0,
|
|
699
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
700
844
|
createdAt: new Date(row.created_at),
|
|
701
845
|
updatedAt: new Date(row.updated_at)
|
|
702
|
-
};
|
|
846
|
+
}));
|
|
703
847
|
}
|
|
704
848
|
/**
|
|
705
|
-
*
|
|
849
|
+
* Close database connection
|
|
706
850
|
*/
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
id: row.id,
|
|
710
|
-
workflowHistoryId: row.workflow_history_id,
|
|
711
|
-
eventId: row.event_id,
|
|
712
|
-
name: row.name,
|
|
713
|
-
type: row.type,
|
|
714
|
-
startTime: row.start_time,
|
|
715
|
-
endTime: row.end_time ? row.end_time : void 0,
|
|
716
|
-
status: row.status,
|
|
717
|
-
level: row.level || void 0,
|
|
718
|
-
input: row.input ? JSON.parse(row.input) : void 0,
|
|
719
|
-
output: row.output ? JSON.parse(row.output) : void 0,
|
|
720
|
-
statusMessage: row.status_message ? JSON.parse(row.status_message) : void 0,
|
|
721
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
722
|
-
traceId: row.trace_id || void 0,
|
|
723
|
-
parentEventId: row.parent_event_id || void 0,
|
|
724
|
-
eventSequence: Number(row.event_sequence),
|
|
725
|
-
createdAt: new Date(row.created_at)
|
|
726
|
-
};
|
|
851
|
+
async close() {
|
|
852
|
+
this.logger.debug("Closing LibSQL Memory adapter");
|
|
727
853
|
}
|
|
728
854
|
};
|
|
729
855
|
|
|
730
|
-
// src/
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
__name(debugDelay, "debugDelay");
|
|
738
|
-
var LibSQLStorage = class {
|
|
856
|
+
// src/observability-adapter.ts
|
|
857
|
+
var import_node_fs2 = require("fs");
|
|
858
|
+
var import_node_path2 = require("path");
|
|
859
|
+
var import_client2 = require("@libsql/client");
|
|
860
|
+
var import_utils = require("@voltagent/internal/utils");
|
|
861
|
+
var import_logger2 = require("@voltagent/logger");
|
|
862
|
+
var LibSQLObservabilityAdapter = class {
|
|
739
863
|
static {
|
|
740
|
-
__name(this, "
|
|
864
|
+
__name(this, "LibSQLObservabilityAdapter");
|
|
741
865
|
}
|
|
742
866
|
client;
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
workflowExtension;
|
|
867
|
+
tablePrefix;
|
|
868
|
+
debug;
|
|
746
869
|
logger;
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
tablePrefix: options.tablePrefix || "voltagent_memory",
|
|
760
|
-
debug: options.debug || false,
|
|
761
|
-
url: options.url || "file:./.voltagent/memory.db",
|
|
762
|
-
authToken: options.authToken,
|
|
763
|
-
retryAttempts: this.retryAttempts,
|
|
764
|
-
baseDelayMs: this.baseDelayMs
|
|
765
|
-
};
|
|
766
|
-
if (this.options.url.startsWith("file:") && !this.options.url.includes(":memory:")) {
|
|
767
|
-
const filePath = this.options.url.substring(5);
|
|
768
|
-
const dir = (0, import_node_path.dirname)(filePath);
|
|
769
|
-
if (dir && dir !== "." && !(0, import_node_fs.existsSync)(dir)) {
|
|
870
|
+
initialized;
|
|
871
|
+
maxSpansPerQuery;
|
|
872
|
+
constructor(options = {}) {
|
|
873
|
+
this.logger = options.logger || (0, import_logger2.createPinoLogger)({ name: "libsql-observability" });
|
|
874
|
+
this.tablePrefix = options.tablePrefix || "observability";
|
|
875
|
+
this.debug = options.debug || false;
|
|
876
|
+
this.maxSpansPerQuery = options.maxSpansPerQuery || 1e3;
|
|
877
|
+
const url = options.url || "file:./.voltagent/observability.db";
|
|
878
|
+
if (url.startsWith("file:") && !url.includes(":memory:")) {
|
|
879
|
+
const filePath = url.substring(5);
|
|
880
|
+
const dir = (0, import_node_path2.dirname)(filePath);
|
|
881
|
+
if (dir && dir !== "." && !(0, import_node_fs2.existsSync)(dir)) {
|
|
770
882
|
try {
|
|
771
|
-
(0,
|
|
772
|
-
this.
|
|
883
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
884
|
+
this.debugLog("Created directory for database", { dir });
|
|
773
885
|
} catch (error) {
|
|
774
886
|
this.logger.warn("Failed to create directory for database", { dir, error });
|
|
775
887
|
}
|
|
776
888
|
}
|
|
777
889
|
}
|
|
778
|
-
this.client = (0,
|
|
779
|
-
url
|
|
780
|
-
authToken:
|
|
890
|
+
this.client = (0, import_client2.createClient)({
|
|
891
|
+
url,
|
|
892
|
+
authToken: options.authToken
|
|
893
|
+
});
|
|
894
|
+
this.debugLog("LibSQL observability adapter initialized with options", {
|
|
895
|
+
url,
|
|
896
|
+
tablePrefix: this.tablePrefix,
|
|
897
|
+
debug: this.debug,
|
|
898
|
+
maxSpansPerQuery: this.maxSpansPerQuery
|
|
781
899
|
});
|
|
782
|
-
this.debug("LibSQL storage provider initialized with options", this.options);
|
|
783
|
-
this.workflowExtension = new LibSQLWorkflowExtension(
|
|
784
|
-
this.client,
|
|
785
|
-
this.options.tablePrefix,
|
|
786
|
-
this.logger
|
|
787
|
-
);
|
|
788
900
|
this.initialized = this.initializeDatabase();
|
|
789
901
|
}
|
|
790
902
|
/**
|
|
791
903
|
* Log a debug message if debug is enabled
|
|
792
|
-
* @param message Message to log
|
|
793
|
-
* @param data Additional data to log
|
|
794
904
|
*/
|
|
795
|
-
|
|
796
|
-
if (this.
|
|
905
|
+
debugLog(message, data) {
|
|
906
|
+
if (this.debug) {
|
|
797
907
|
this.logger.debug(`${message}`, data || "");
|
|
798
908
|
}
|
|
799
909
|
}
|
|
800
910
|
/**
|
|
801
|
-
*
|
|
802
|
-
* @param attempt Current retry attempt number
|
|
803
|
-
* @returns Delay in milliseconds
|
|
804
|
-
*/
|
|
805
|
-
calculateRetryDelay(attempt) {
|
|
806
|
-
const exponentialDelay = this.baseDelayMs * 2 ** (attempt - 1);
|
|
807
|
-
const jitterFactor = 0.2 + Math.random() * 0.2;
|
|
808
|
-
const delayWithJitter = exponentialDelay * (1 + jitterFactor);
|
|
809
|
-
return Math.min(delayWithJitter, 2e3);
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Execute a database operation with retry strategy
|
|
813
|
-
* Implements jittered exponential backoff
|
|
814
|
-
* @param operationFn The operation function to execute
|
|
815
|
-
* @param operationName Operation name for logging
|
|
816
|
-
* @returns The result of the operation
|
|
817
|
-
*/
|
|
818
|
-
async executeWithRetryStrategy(operationFn, operationName) {
|
|
819
|
-
let attempt = 0;
|
|
820
|
-
while (attempt < this.retryAttempts) {
|
|
821
|
-
attempt++;
|
|
822
|
-
try {
|
|
823
|
-
return await operationFn();
|
|
824
|
-
} catch (error) {
|
|
825
|
-
const isBusyError = error.message && (error.message.includes("SQLITE_BUSY") || error.message.includes("database is locked") || error.code === "SQLITE_BUSY");
|
|
826
|
-
if (!isBusyError || attempt >= this.retryAttempts) {
|
|
827
|
-
this.debug(`Operation failed: ${operationName}`, {
|
|
828
|
-
attempt,
|
|
829
|
-
error: error.message
|
|
830
|
-
});
|
|
831
|
-
throw error;
|
|
832
|
-
}
|
|
833
|
-
const delay = this.calculateRetryDelay(attempt);
|
|
834
|
-
this.debug(`Retrying ${operationName}`, {
|
|
835
|
-
attempt,
|
|
836
|
-
remainingAttempts: this.retryAttempts - attempt,
|
|
837
|
-
delay
|
|
838
|
-
});
|
|
839
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
throw new Error(`Max retry attempts (${this.retryAttempts}) exceeded for ${operationName}`);
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* Initialize workflow tables
|
|
846
|
-
*/
|
|
847
|
-
async initializeWorkflowTables() {
|
|
848
|
-
try {
|
|
849
|
-
await createWorkflowTables(this.client, this.options.tablePrefix);
|
|
850
|
-
this.debug("Workflow tables initialized successfully");
|
|
851
|
-
await addSuspendedStatusMigration(this.client, this.options.tablePrefix);
|
|
852
|
-
this.debug("Workflow migrations applied successfully");
|
|
853
|
-
} catch (error) {
|
|
854
|
-
this.debug("Error initializing workflow tables:", error);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Initialize the database tables
|
|
859
|
-
* @returns Promise that resolves when initialization is complete
|
|
911
|
+
* Initialize database tables for observability
|
|
860
912
|
*/
|
|
861
913
|
async initializeDatabase() {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
created_at TEXT NOT NULL,
|
|
885
|
-
updated_at TEXT NOT NULL
|
|
914
|
+
try {
|
|
915
|
+
await this.client.execute(`
|
|
916
|
+
CREATE TABLE IF NOT EXISTS ${this.tablePrefix}_spans (
|
|
917
|
+
span_id TEXT PRIMARY KEY,
|
|
918
|
+
trace_id TEXT NOT NULL,
|
|
919
|
+
parent_span_id TEXT,
|
|
920
|
+
entity_id TEXT,
|
|
921
|
+
entity_type TEXT,
|
|
922
|
+
name TEXT NOT NULL,
|
|
923
|
+
kind INTEGER DEFAULT 0,
|
|
924
|
+
start_time TEXT NOT NULL,
|
|
925
|
+
end_time TEXT,
|
|
926
|
+
duration REAL,
|
|
927
|
+
status_code INTEGER DEFAULT 0,
|
|
928
|
+
status_message TEXT,
|
|
929
|
+
attributes TEXT,
|
|
930
|
+
events TEXT,
|
|
931
|
+
links TEXT,
|
|
932
|
+
resource TEXT,
|
|
933
|
+
instrumentation_scope TEXT,
|
|
934
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
935
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
886
936
|
)
|
|
887
937
|
`);
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
conversation_id TEXT NOT NULL,
|
|
892
|
-
message_id TEXT NOT NULL,
|
|
893
|
-
role TEXT NOT NULL,
|
|
894
|
-
content TEXT NOT NULL,
|
|
895
|
-
type TEXT NOT NULL,
|
|
896
|
-
created_at TEXT NOT NULL,
|
|
897
|
-
PRIMARY KEY (conversation_id, message_id)
|
|
898
|
-
)
|
|
938
|
+
await this.client.execute(`
|
|
939
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_spans_trace_id
|
|
940
|
+
ON ${this.tablePrefix}_spans(trace_id)
|
|
899
941
|
`);
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
id TEXT PRIMARY KEY,
|
|
904
|
-
agent_id TEXT NOT NULL,
|
|
905
|
-
timestamp TEXT NOT NULL,
|
|
906
|
-
status TEXT,
|
|
907
|
-
input TEXT,
|
|
908
|
-
output TEXT,
|
|
909
|
-
usage TEXT,
|
|
910
|
-
metadata TEXT,
|
|
911
|
-
userId TEXT,
|
|
912
|
-
conversationId TEXT
|
|
913
|
-
)
|
|
942
|
+
await this.client.execute(`
|
|
943
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_spans_parent_span_id
|
|
944
|
+
ON ${this.tablePrefix}_spans(parent_span_id)
|
|
914
945
|
`);
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
key TEXT PRIMARY KEY,
|
|
919
|
-
value TEXT NOT NULL,
|
|
920
|
-
history_id TEXT NOT NULL,
|
|
921
|
-
agent_id TEXT
|
|
922
|
-
)
|
|
946
|
+
await this.client.execute(`
|
|
947
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_spans_start_time
|
|
948
|
+
ON ${this.tablePrefix}_spans(start_time)
|
|
923
949
|
`);
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
id TEXT PRIMARY KEY,
|
|
928
|
-
history_id TEXT NOT NULL,
|
|
929
|
-
agent_id TEXT,
|
|
930
|
-
event_type TEXT NOT NULL,
|
|
931
|
-
event_name TEXT NOT NULL,
|
|
932
|
-
start_time TEXT NOT NULL,
|
|
933
|
-
end_time TEXT,
|
|
934
|
-
status TEXT,
|
|
935
|
-
status_message TEXT,
|
|
936
|
-
level TEXT,
|
|
937
|
-
version TEXT,
|
|
938
|
-
parent_event_id TEXT,
|
|
939
|
-
tags TEXT,
|
|
940
|
-
input TEXT,
|
|
941
|
-
output TEXT,
|
|
942
|
-
error TEXT,
|
|
943
|
-
metadata TEXT
|
|
944
|
-
)
|
|
950
|
+
await this.client.execute(`
|
|
951
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_spans_name
|
|
952
|
+
ON ${this.tablePrefix}_spans(name)
|
|
945
953
|
`);
|
|
946
|
-
|
|
947
|
-
CREATE INDEX IF NOT EXISTS idx_${
|
|
948
|
-
ON ${
|
|
954
|
+
await this.client.execute(`
|
|
955
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_spans_entity_id
|
|
956
|
+
ON ${this.tablePrefix}_spans(entity_id)
|
|
949
957
|
`);
|
|
950
|
-
|
|
951
|
-
CREATE INDEX IF NOT EXISTS idx_${
|
|
952
|
-
ON ${
|
|
958
|
+
await this.client.execute(`
|
|
959
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_spans_entity_type
|
|
960
|
+
ON ${this.tablePrefix}_spans(entity_type)
|
|
953
961
|
`);
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
await this.client.execute(`
|
|
967
|
-
CREATE INDEX IF NOT EXISTS idx_${historyStepsTableName}_history_id
|
|
968
|
-
ON ${historyStepsTableName}(history_id)
|
|
962
|
+
await this.client.execute(`
|
|
963
|
+
CREATE TABLE IF NOT EXISTS ${this.tablePrefix}_traces (
|
|
964
|
+
trace_id TEXT PRIMARY KEY,
|
|
965
|
+
root_span_id TEXT,
|
|
966
|
+
entity_id TEXT,
|
|
967
|
+
entity_type TEXT,
|
|
968
|
+
start_time TEXT NOT NULL,
|
|
969
|
+
end_time TEXT,
|
|
970
|
+
span_count INTEGER DEFAULT 1,
|
|
971
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
972
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
973
|
+
)
|
|
969
974
|
`);
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
ON ${historyTableName}(agent_id)
|
|
975
|
+
await this.client.execute(`
|
|
976
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_traces_start_time
|
|
977
|
+
ON ${this.tablePrefix}_traces(start_time DESC)
|
|
974
978
|
`);
|
|
975
|
-
|
|
976
|
-
CREATE INDEX IF NOT EXISTS idx_${
|
|
977
|
-
ON ${
|
|
979
|
+
await this.client.execute(`
|
|
980
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_traces_entity_id
|
|
981
|
+
ON ${this.tablePrefix}_traces(entity_id)
|
|
978
982
|
`);
|
|
979
|
-
|
|
980
|
-
CREATE INDEX IF NOT EXISTS idx_${
|
|
981
|
-
ON ${
|
|
983
|
+
await this.client.execute(`
|
|
984
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_traces_entity_type
|
|
985
|
+
ON ${this.tablePrefix}_traces(entity_type)
|
|
982
986
|
`);
|
|
983
|
-
|
|
984
|
-
CREATE
|
|
985
|
-
|
|
987
|
+
await this.client.execute(`
|
|
988
|
+
CREATE TABLE IF NOT EXISTS ${this.tablePrefix}_logs (
|
|
989
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
990
|
+
timestamp TEXT NOT NULL,
|
|
991
|
+
trace_id TEXT,
|
|
992
|
+
span_id TEXT,
|
|
993
|
+
trace_flags INTEGER,
|
|
994
|
+
severity_number INTEGER,
|
|
995
|
+
severity_text TEXT,
|
|
996
|
+
body TEXT NOT NULL,
|
|
997
|
+
attributes TEXT,
|
|
998
|
+
resource TEXT,
|
|
999
|
+
instrumentation_scope TEXT,
|
|
1000
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
1001
|
+
)
|
|
986
1002
|
`);
|
|
987
|
-
|
|
988
|
-
CREATE INDEX IF NOT EXISTS idx_${
|
|
989
|
-
ON ${
|
|
1003
|
+
await this.client.execute(`
|
|
1004
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_logs_trace_id
|
|
1005
|
+
ON ${this.tablePrefix}_logs(trace_id)
|
|
990
1006
|
`);
|
|
991
|
-
|
|
992
|
-
CREATE INDEX IF NOT EXISTS idx_${
|
|
993
|
-
ON ${
|
|
1007
|
+
await this.client.execute(`
|
|
1008
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_logs_span_id
|
|
1009
|
+
ON ${this.tablePrefix}_logs(span_id)
|
|
994
1010
|
`);
|
|
995
|
-
|
|
996
|
-
CREATE INDEX IF NOT EXISTS idx_${
|
|
997
|
-
ON ${
|
|
1011
|
+
await this.client.execute(`
|
|
1012
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_logs_timestamp
|
|
1013
|
+
ON ${this.tablePrefix}_logs(timestamp DESC)
|
|
998
1014
|
`);
|
|
999
|
-
|
|
1000
|
-
CREATE INDEX IF NOT EXISTS idx_${
|
|
1001
|
-
ON ${
|
|
1015
|
+
await this.client.execute(`
|
|
1016
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tablePrefix}_logs_severity
|
|
1017
|
+
ON ${this.tablePrefix}_logs(severity_number)
|
|
1002
1018
|
`);
|
|
1003
|
-
|
|
1004
|
-
try {
|
|
1005
|
-
const migrationResult = await this.migrateConversationSchema({
|
|
1006
|
-
createBackup: true,
|
|
1007
|
-
deleteBackupAfterSuccess: true
|
|
1008
|
-
});
|
|
1009
|
-
if (migrationResult.success) {
|
|
1010
|
-
if ((migrationResult.migratedCount || 0) > 0) {
|
|
1011
|
-
this.logger.info(
|
|
1012
|
-
`${migrationResult.migratedCount} conversation records successfully migrated`
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
} else {
|
|
1016
|
-
this.logger.error("Conversation migration error:", migrationResult.error);
|
|
1017
|
-
}
|
|
1018
|
-
} catch (error) {
|
|
1019
|
-
this.debug("Error migrating conversation schema:", error);
|
|
1020
|
-
}
|
|
1021
|
-
try {
|
|
1022
|
-
const migrationResult = await this.migrateAgentHistorySchema();
|
|
1023
|
-
if (!migrationResult.success) {
|
|
1024
|
-
this.logger.error("Agent history schema migration error:", migrationResult.error);
|
|
1025
|
-
}
|
|
1019
|
+
this.debugLog("Database tables initialized successfully");
|
|
1026
1020
|
} catch (error) {
|
|
1027
|
-
this.
|
|
1021
|
+
this.logger.error("Failed to initialize database tables", { error });
|
|
1022
|
+
throw error;
|
|
1028
1023
|
}
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Ensure database is initialized before operations
|
|
1027
|
+
*/
|
|
1028
|
+
async ensureInitialized() {
|
|
1029
|
+
await this.initialized;
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Add a span to the database
|
|
1033
|
+
*/
|
|
1034
|
+
async addSpan(span) {
|
|
1035
|
+
await this.ensureInitialized();
|
|
1029
1036
|
try {
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1037
|
+
const entityId = span.attributes?.["entity.id"] || null;
|
|
1038
|
+
const entityType = span.attributes?.["entity.type"] || null;
|
|
1039
|
+
await this.client.batch([
|
|
1040
|
+
// Insert the span with entity columns
|
|
1041
|
+
{
|
|
1042
|
+
sql: `
|
|
1043
|
+
INSERT INTO ${this.tablePrefix}_spans (
|
|
1044
|
+
span_id, trace_id, parent_span_id, entity_id, entity_type, name, kind,
|
|
1045
|
+
start_time, end_time, duration,
|
|
1046
|
+
status_code, status_message,
|
|
1047
|
+
attributes, events, links,
|
|
1048
|
+
resource, instrumentation_scope
|
|
1049
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1050
|
+
`,
|
|
1051
|
+
args: [
|
|
1052
|
+
span.spanId,
|
|
1053
|
+
span.traceId,
|
|
1054
|
+
span.parentSpanId || null,
|
|
1055
|
+
entityId,
|
|
1056
|
+
entityType,
|
|
1057
|
+
span.name,
|
|
1058
|
+
span.kind,
|
|
1059
|
+
span.startTime,
|
|
1060
|
+
span.endTime || null,
|
|
1061
|
+
span.duration || null,
|
|
1062
|
+
span.status.code,
|
|
1063
|
+
span.status.message || null,
|
|
1064
|
+
(0, import_utils.safeStringify)(span.attributes),
|
|
1065
|
+
(0, import_utils.safeStringify)(span.events),
|
|
1066
|
+
span.links ? (0, import_utils.safeStringify)(span.links) : null,
|
|
1067
|
+
span.resource ? (0, import_utils.safeStringify)(span.resource) : null,
|
|
1068
|
+
span.instrumentationScope ? (0, import_utils.safeStringify)(span.instrumentationScope) : null
|
|
1069
|
+
]
|
|
1070
|
+
},
|
|
1071
|
+
// Update or insert trace metadata with entity columns
|
|
1072
|
+
{
|
|
1073
|
+
sql: `
|
|
1074
|
+
INSERT INTO ${this.tablePrefix}_traces (
|
|
1075
|
+
trace_id, root_span_id, entity_id, entity_type, start_time, end_time, span_count
|
|
1076
|
+
) VALUES (?, ?, ?, ?, ?, ?, 1)
|
|
1077
|
+
ON CONFLICT(trace_id) DO UPDATE SET
|
|
1078
|
+
span_count = span_count + 1,
|
|
1079
|
+
entity_id = COALESCE(excluded.entity_id, entity_id),
|
|
1080
|
+
entity_type = COALESCE(excluded.entity_type, entity_type),
|
|
1081
|
+
start_time = MIN(start_time, excluded.start_time),
|
|
1082
|
+
end_time = MAX(COALESCE(end_time, excluded.end_time), excluded.end_time),
|
|
1083
|
+
updated_at = CURRENT_TIMESTAMP
|
|
1084
|
+
`,
|
|
1085
|
+
args: [
|
|
1086
|
+
span.traceId,
|
|
1087
|
+
span.parentSpanId ? null : span.spanId,
|
|
1088
|
+
// Root span if no parent
|
|
1089
|
+
entityId,
|
|
1090
|
+
entityType,
|
|
1091
|
+
span.startTime,
|
|
1092
|
+
span.endTime || null
|
|
1093
|
+
]
|
|
1042
1094
|
}
|
|
1043
|
-
|
|
1095
|
+
]);
|
|
1096
|
+
this.debugLog("Span added successfully", {
|
|
1097
|
+
spanId: span.spanId,
|
|
1098
|
+
traceId: span.traceId
|
|
1099
|
+
});
|
|
1044
1100
|
} catch (error) {
|
|
1045
|
-
this.
|
|
1101
|
+
this.logger.error("Failed to add span", { error, span });
|
|
1102
|
+
throw error;
|
|
1046
1103
|
}
|
|
1047
1104
|
}
|
|
1048
1105
|
/**
|
|
1049
|
-
*
|
|
1050
|
-
* @returns Unique ID
|
|
1051
|
-
*/
|
|
1052
|
-
generateId() {
|
|
1053
|
-
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Get messages with filtering options
|
|
1057
|
-
* @param options Filtering options
|
|
1058
|
-
* @returns Filtered messages
|
|
1106
|
+
* Update an existing span
|
|
1059
1107
|
*/
|
|
1060
|
-
async
|
|
1061
|
-
await this.
|
|
1062
|
-
await debugDelay();
|
|
1063
|
-
const {
|
|
1064
|
-
userId = "default",
|
|
1065
|
-
conversationId = "default",
|
|
1066
|
-
limit,
|
|
1067
|
-
before,
|
|
1068
|
-
after,
|
|
1069
|
-
role,
|
|
1070
|
-
types
|
|
1071
|
-
} = options;
|
|
1072
|
-
const messagesTableName = `${this.options.tablePrefix}_messages`;
|
|
1073
|
-
const conversationsTableName = `${this.options.tablePrefix}_conversations`;
|
|
1108
|
+
async updateSpan(spanId, updates) {
|
|
1109
|
+
await this.ensureInitialized();
|
|
1074
1110
|
try {
|
|
1075
|
-
|
|
1076
|
-
SELECT m.message_id, m.role, m.content, m.type, m.created_at, m.conversation_id
|
|
1077
|
-
FROM ${messagesTableName} m
|
|
1078
|
-
`;
|
|
1111
|
+
const setClauses = [];
|
|
1079
1112
|
const args = [];
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
conditions.push("c.user_id = ?");
|
|
1084
|
-
args.push(userId);
|
|
1113
|
+
if (updates.endTime !== void 0) {
|
|
1114
|
+
setClauses.push("end_time = ?");
|
|
1115
|
+
args.push(updates.endTime);
|
|
1085
1116
|
}
|
|
1086
|
-
if (
|
|
1087
|
-
|
|
1088
|
-
args.push(
|
|
1117
|
+
if (updates.duration !== void 0) {
|
|
1118
|
+
setClauses.push("duration = ?");
|
|
1119
|
+
args.push(updates.duration);
|
|
1089
1120
|
}
|
|
1090
|
-
if (
|
|
1091
|
-
|
|
1092
|
-
args.push(
|
|
1121
|
+
if (updates.status !== void 0) {
|
|
1122
|
+
setClauses.push("status_code = ?, status_message = ?");
|
|
1123
|
+
args.push(updates.status.code, updates.status.message || null);
|
|
1093
1124
|
}
|
|
1094
|
-
if (
|
|
1095
|
-
|
|
1096
|
-
args.push(
|
|
1125
|
+
if (updates.attributes !== void 0) {
|
|
1126
|
+
setClauses.push("attributes = ?");
|
|
1127
|
+
args.push((0, import_utils.safeStringify)(updates.attributes));
|
|
1097
1128
|
}
|
|
1098
|
-
if (
|
|
1099
|
-
|
|
1100
|
-
args.push(
|
|
1129
|
+
if (updates.events !== void 0) {
|
|
1130
|
+
setClauses.push("events = ?");
|
|
1131
|
+
args.push((0, import_utils.safeStringify)(updates.events));
|
|
1101
1132
|
}
|
|
1102
|
-
if (
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
args.push(...types);
|
|
1133
|
+
if (updates.links !== void 0) {
|
|
1134
|
+
setClauses.push("links = ?");
|
|
1135
|
+
args.push((0, import_utils.safeStringify)(updates.links));
|
|
1106
1136
|
}
|
|
1107
|
-
if (
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
if (limit && limit > 0) {
|
|
1111
|
-
sql += " ORDER BY m.created_at DESC LIMIT ?";
|
|
1112
|
-
args.push(limit);
|
|
1113
|
-
} else {
|
|
1114
|
-
sql += " ORDER BY m.created_at ASC";
|
|
1137
|
+
if (setClauses.length === 0) {
|
|
1138
|
+
return;
|
|
1115
1139
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1140
|
+
setClauses.push("updated_at = CURRENT_TIMESTAMP");
|
|
1141
|
+
args.push(spanId);
|
|
1142
|
+
await this.client.execute({
|
|
1143
|
+
sql: `
|
|
1144
|
+
UPDATE ${this.tablePrefix}_spans
|
|
1145
|
+
SET ${setClauses.join(", ")}
|
|
1146
|
+
WHERE span_id = ?
|
|
1147
|
+
`,
|
|
1118
1148
|
args
|
|
1119
1149
|
});
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1150
|
+
if (updates.endTime) {
|
|
1151
|
+
const span = await this.getSpan(spanId);
|
|
1152
|
+
if (span) {
|
|
1153
|
+
await this.client.execute({
|
|
1154
|
+
sql: `
|
|
1155
|
+
UPDATE ${this.tablePrefix}_traces
|
|
1156
|
+
SET end_time = MAX(COALESCE(end_time, ?), ?),
|
|
1157
|
+
updated_at = CURRENT_TIMESTAMP
|
|
1158
|
+
WHERE trace_id = ?
|
|
1159
|
+
`,
|
|
1160
|
+
args: [updates.endTime, updates.endTime, span.traceId]
|
|
1161
|
+
});
|
|
1125
1162
|
}
|
|
1126
|
-
return {
|
|
1127
|
-
id: row.message_id,
|
|
1128
|
-
role: row.role,
|
|
1129
|
-
content,
|
|
1130
|
-
type: row.type,
|
|
1131
|
-
createdAt: row.created_at
|
|
1132
|
-
};
|
|
1133
|
-
});
|
|
1134
|
-
if (limit && limit > 0) {
|
|
1135
|
-
return messages.reverse();
|
|
1136
1163
|
}
|
|
1137
|
-
|
|
1164
|
+
this.debugLog("Span updated successfully", { spanId, updates });
|
|
1138
1165
|
} catch (error) {
|
|
1139
|
-
this.
|
|
1140
|
-
throw
|
|
1166
|
+
this.logger.error("Failed to update span", { error, spanId, updates });
|
|
1167
|
+
throw error;
|
|
1141
1168
|
}
|
|
1142
1169
|
}
|
|
1143
1170
|
/**
|
|
1144
|
-
*
|
|
1145
|
-
* @param message Message to add
|
|
1146
|
-
* @param userId User identifier (optional, defaults to "default")
|
|
1147
|
-
* @param conversationId Conversation identifier (optional, defaults to "default")
|
|
1171
|
+
* Get a span by ID
|
|
1148
1172
|
*/
|
|
1149
|
-
async
|
|
1150
|
-
await this.
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
args: [
|
|
1159
|
-
conversationId,
|
|
1160
|
-
message.id,
|
|
1161
|
-
message.role,
|
|
1162
|
-
contentString,
|
|
1163
|
-
message.type,
|
|
1164
|
-
message.createdAt
|
|
1165
|
-
]
|
|
1173
|
+
async getSpan(spanId) {
|
|
1174
|
+
await this.ensureInitialized();
|
|
1175
|
+
try {
|
|
1176
|
+
const result = await this.client.execute({
|
|
1177
|
+
sql: `
|
|
1178
|
+
SELECT * FROM ${this.tablePrefix}_spans
|
|
1179
|
+
WHERE span_id = ?
|
|
1180
|
+
`,
|
|
1181
|
+
args: [spanId]
|
|
1166
1182
|
});
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
await this.pruneOldMessages(conversationId);
|
|
1170
|
-
} catch (pruneError) {
|
|
1171
|
-
this.debug("Error pruning old messages:", pruneError);
|
|
1183
|
+
if (result.rows.length === 0) {
|
|
1184
|
+
return null;
|
|
1172
1185
|
}
|
|
1173
|
-
|
|
1186
|
+
const row = result.rows[0];
|
|
1187
|
+
return this.rowToSpan(row);
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
this.logger.error("Failed to get span", { error, spanId });
|
|
1190
|
+
throw error;
|
|
1191
|
+
}
|
|
1174
1192
|
}
|
|
1175
1193
|
/**
|
|
1176
|
-
*
|
|
1177
|
-
* @param conversationId Conversation ID to prune messages for
|
|
1194
|
+
* Get all spans in a trace
|
|
1178
1195
|
*/
|
|
1179
|
-
async
|
|
1180
|
-
|
|
1181
|
-
const tableName = `${this.options.tablePrefix}_messages`;
|
|
1196
|
+
async getTrace(traceId) {
|
|
1197
|
+
await this.ensureInitialized();
|
|
1182
1198
|
try {
|
|
1183
|
-
const
|
|
1184
|
-
sql: `
|
|
1185
|
-
|
|
1199
|
+
const result = await this.client.execute({
|
|
1200
|
+
sql: `
|
|
1201
|
+
SELECT * FROM ${this.tablePrefix}_spans
|
|
1202
|
+
WHERE trace_id = ?
|
|
1203
|
+
ORDER BY start_time ASC
|
|
1204
|
+
LIMIT ?
|
|
1205
|
+
`,
|
|
1206
|
+
args: [traceId, this.maxSpansPerQuery]
|
|
1186
1207
|
});
|
|
1187
|
-
|
|
1188
|
-
if (messageCount > limit) {
|
|
1189
|
-
const deleteCount = messageCount - limit;
|
|
1190
|
-
await this.client.execute({
|
|
1191
|
-
sql: `DELETE FROM ${tableName}
|
|
1192
|
-
WHERE conversation_id = ?
|
|
1193
|
-
AND message_id IN (
|
|
1194
|
-
SELECT message_id FROM ${tableName}
|
|
1195
|
-
WHERE conversation_id = ?
|
|
1196
|
-
ORDER BY created_at ASC
|
|
1197
|
-
LIMIT ?
|
|
1198
|
-
)`,
|
|
1199
|
-
args: [conversationId, conversationId, deleteCount]
|
|
1200
|
-
});
|
|
1201
|
-
this.debug(`Pruned ${deleteCount} old messages for conversation ${conversationId}`);
|
|
1202
|
-
}
|
|
1208
|
+
return result.rows.map((row) => this.rowToSpan(row));
|
|
1203
1209
|
} catch (error) {
|
|
1204
|
-
this.
|
|
1210
|
+
this.logger.error("Failed to get trace", { error, traceId });
|
|
1205
1211
|
throw error;
|
|
1206
1212
|
}
|
|
1207
1213
|
}
|
|
1208
1214
|
/**
|
|
1209
|
-
*
|
|
1215
|
+
* List all traces with optional entity filter
|
|
1210
1216
|
*/
|
|
1211
|
-
async
|
|
1212
|
-
await this.
|
|
1213
|
-
await debugDelay();
|
|
1214
|
-
const { userId, conversationId } = options;
|
|
1215
|
-
const messagesTableName = `${this.options.tablePrefix}_messages`;
|
|
1216
|
-
const conversationsTableName = `${this.options.tablePrefix}_conversations`;
|
|
1217
|
+
async listTraces(limit = 100, offset = 0, filter) {
|
|
1218
|
+
await this.ensureInitialized();
|
|
1217
1219
|
try {
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1220
|
+
let sql;
|
|
1221
|
+
let args = [];
|
|
1222
|
+
const conditions = [];
|
|
1223
|
+
if (filter?.entityId) {
|
|
1224
|
+
conditions.push("entity_id = ?");
|
|
1225
|
+
args.push(filter.entityId);
|
|
1226
|
+
}
|
|
1227
|
+
if (filter?.entityType) {
|
|
1228
|
+
conditions.push("entity_type = ?");
|
|
1229
|
+
args.push(filter.entityType);
|
|
1230
|
+
}
|
|
1231
|
+
if (conditions.length > 0) {
|
|
1232
|
+
sql = `
|
|
1233
|
+
SELECT trace_id FROM ${this.tablePrefix}_traces
|
|
1234
|
+
WHERE ${conditions.join(" AND ")}
|
|
1235
|
+
ORDER BY start_time DESC
|
|
1236
|
+
LIMIT ? OFFSET ?
|
|
1237
|
+
`;
|
|
1238
|
+
args.push(limit, offset);
|
|
1228
1239
|
} else {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
});
|
|
1236
|
-
this.debug(`Cleared all messages for user ${userId}`);
|
|
1240
|
+
sql = `
|
|
1241
|
+
SELECT trace_id FROM ${this.tablePrefix}_traces
|
|
1242
|
+
ORDER BY start_time DESC
|
|
1243
|
+
LIMIT ? OFFSET ?
|
|
1244
|
+
`;
|
|
1245
|
+
args = [limit, offset];
|
|
1237
1246
|
}
|
|
1247
|
+
const result = await this.client.execute({ sql, args });
|
|
1248
|
+
return result.rows.map((row) => row.trace_id);
|
|
1238
1249
|
} catch (error) {
|
|
1239
|
-
this.
|
|
1240
|
-
throw
|
|
1250
|
+
this.logger.error("Failed to list traces", { error, limit, offset, filter });
|
|
1251
|
+
throw error;
|
|
1241
1252
|
}
|
|
1242
1253
|
}
|
|
1243
1254
|
/**
|
|
1244
|
-
*
|
|
1255
|
+
* Delete old spans
|
|
1245
1256
|
*/
|
|
1246
|
-
async
|
|
1257
|
+
async deleteOldSpans(beforeTimestamp) {
|
|
1258
|
+
await this.ensureInitialized();
|
|
1247
1259
|
try {
|
|
1248
|
-
|
|
1249
|
-
|
|
1260
|
+
const beforeDate = new Date(beforeTimestamp).toISOString();
|
|
1261
|
+
const tracesResult = await this.client.execute({
|
|
1262
|
+
sql: `
|
|
1263
|
+
SELECT DISTINCT trace_id FROM ${this.tablePrefix}_spans
|
|
1264
|
+
WHERE start_time < ?
|
|
1265
|
+
`,
|
|
1266
|
+
args: [beforeDate]
|
|
1267
|
+
});
|
|
1268
|
+
const affectedTraceIds = tracesResult.rows.map((row) => row.trace_id);
|
|
1269
|
+
const deleteResult = await this.client.execute({
|
|
1270
|
+
sql: `
|
|
1271
|
+
DELETE FROM ${this.tablePrefix}_spans
|
|
1272
|
+
WHERE start_time < ?
|
|
1273
|
+
`,
|
|
1274
|
+
args: [beforeDate]
|
|
1275
|
+
});
|
|
1276
|
+
if (affectedTraceIds.length > 0) {
|
|
1277
|
+
for (const traceId of affectedTraceIds) {
|
|
1278
|
+
const countResult = await this.client.execute({
|
|
1279
|
+
sql: `
|
|
1280
|
+
SELECT COUNT(*) as count FROM ${this.tablePrefix}_spans
|
|
1281
|
+
WHERE trace_id = ?
|
|
1282
|
+
`,
|
|
1283
|
+
args: [traceId]
|
|
1284
|
+
});
|
|
1285
|
+
const count = countResult.rows[0].count;
|
|
1286
|
+
if (count === 0) {
|
|
1287
|
+
await this.client.execute({
|
|
1288
|
+
sql: `
|
|
1289
|
+
DELETE FROM ${this.tablePrefix}_traces
|
|
1290
|
+
WHERE trace_id = ?
|
|
1291
|
+
`,
|
|
1292
|
+
args: [traceId]
|
|
1293
|
+
});
|
|
1294
|
+
} else {
|
|
1295
|
+
await this.client.execute({
|
|
1296
|
+
sql: `
|
|
1297
|
+
UPDATE ${this.tablePrefix}_traces
|
|
1298
|
+
SET span_count = ?,
|
|
1299
|
+
updated_at = CURRENT_TIMESTAMP
|
|
1300
|
+
WHERE trace_id = ?
|
|
1301
|
+
`,
|
|
1302
|
+
args: [count, traceId]
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
const deletedCount = deleteResult.rowsAffected || 0;
|
|
1308
|
+
this.debugLog("Old spans deleted", { deletedCount, beforeDate });
|
|
1309
|
+
return deletedCount;
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
this.logger.error("Failed to delete old spans", { error, beforeTimestamp });
|
|
1312
|
+
throw error;
|
|
1250
1313
|
}
|
|
1251
|
-
this.client.close();
|
|
1252
1314
|
}
|
|
1253
1315
|
/**
|
|
1254
|
-
*
|
|
1255
|
-
* @param key Entry ID
|
|
1256
|
-
* @param value Entry data
|
|
1257
|
-
* @param agentId Agent ID for filtering
|
|
1316
|
+
* Clear all spans, traces, and logs
|
|
1258
1317
|
*/
|
|
1259
|
-
async
|
|
1260
|
-
await this.
|
|
1318
|
+
async clear() {
|
|
1319
|
+
await this.ensureInitialized();
|
|
1261
1320
|
try {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
sql: `INSERT OR REPLACE INTO ${tableName}
|
|
1269
|
-
(id, agent_id, timestamp, status, input, output, usage, metadata, userId, conversationId)
|
|
1270
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1271
|
-
args: [
|
|
1272
|
-
key,
|
|
1273
|
-
// id
|
|
1274
|
-
agentId,
|
|
1275
|
-
// agent_id
|
|
1276
|
-
value.timestamp ? value.timestamp.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1277
|
-
// timestamp
|
|
1278
|
-
value.status || null,
|
|
1279
|
-
// status
|
|
1280
|
-
inputJSON,
|
|
1281
|
-
// input
|
|
1282
|
-
outputJSON,
|
|
1283
|
-
// output
|
|
1284
|
-
usageJSON,
|
|
1285
|
-
// usage
|
|
1286
|
-
metadataJSON,
|
|
1287
|
-
// metadata
|
|
1288
|
-
value.userId || null,
|
|
1289
|
-
// userId
|
|
1290
|
-
value.conversationId || null
|
|
1291
|
-
// conversationId
|
|
1292
|
-
]
|
|
1293
|
-
});
|
|
1294
|
-
this.debug(`Set agent_history entry with ID ${key} for agent ${agentId}`);
|
|
1321
|
+
await this.client.batch([
|
|
1322
|
+
{ sql: `DELETE FROM ${this.tablePrefix}_spans`, args: [] },
|
|
1323
|
+
{ sql: `DELETE FROM ${this.tablePrefix}_traces`, args: [] },
|
|
1324
|
+
{ sql: `DELETE FROM ${this.tablePrefix}_logs`, args: [] }
|
|
1325
|
+
]);
|
|
1326
|
+
this.debugLog("All spans, traces, and logs cleared");
|
|
1295
1327
|
} catch (error) {
|
|
1296
|
-
this.
|
|
1297
|
-
throw
|
|
1328
|
+
this.logger.error("Failed to clear data", { error });
|
|
1329
|
+
throw error;
|
|
1298
1330
|
}
|
|
1299
1331
|
}
|
|
1300
1332
|
/**
|
|
1301
|
-
*
|
|
1302
|
-
* @param key Entry ID
|
|
1303
|
-
* @param value Updated entry data
|
|
1304
|
-
* @param agentId Agent ID for filtering
|
|
1333
|
+
* Convert a database row to an ObservabilitySpan
|
|
1305
1334
|
*/
|
|
1306
|
-
|
|
1307
|
-
|
|
1335
|
+
rowToSpan(row) {
|
|
1336
|
+
const span = {
|
|
1337
|
+
traceId: row.trace_id,
|
|
1338
|
+
spanId: row.span_id,
|
|
1339
|
+
name: row.name,
|
|
1340
|
+
kind: row.kind,
|
|
1341
|
+
startTime: row.start_time,
|
|
1342
|
+
status: {
|
|
1343
|
+
code: row.status_code
|
|
1344
|
+
},
|
|
1345
|
+
attributes: row.attributes ? JSON.parse(row.attributes) : {},
|
|
1346
|
+
events: row.events ? JSON.parse(row.events) : []
|
|
1347
|
+
};
|
|
1348
|
+
if (row.parent_span_id !== null) {
|
|
1349
|
+
span.parentSpanId = row.parent_span_id;
|
|
1350
|
+
}
|
|
1351
|
+
if (row.end_time !== null) {
|
|
1352
|
+
span.endTime = row.end_time;
|
|
1353
|
+
}
|
|
1354
|
+
if (row.duration !== null) {
|
|
1355
|
+
span.duration = row.duration;
|
|
1356
|
+
}
|
|
1357
|
+
if (row.status_message !== null) {
|
|
1358
|
+
span.status.message = row.status_message;
|
|
1359
|
+
}
|
|
1360
|
+
if (row.links && row.links !== "null") {
|
|
1361
|
+
const links = JSON.parse(row.links);
|
|
1362
|
+
if (links && links.length > 0) {
|
|
1363
|
+
span.links = links;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (row.resource && row.resource !== "null") {
|
|
1367
|
+
const resource = JSON.parse(row.resource);
|
|
1368
|
+
if (resource && Object.keys(resource).length > 0) {
|
|
1369
|
+
span.resource = resource;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
if (row.instrumentation_scope && row.instrumentation_scope !== "null") {
|
|
1373
|
+
const scope = JSON.parse(row.instrumentation_scope);
|
|
1374
|
+
if (scope) {
|
|
1375
|
+
span.instrumentationScope = scope;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return span;
|
|
1308
1379
|
}
|
|
1309
1380
|
/**
|
|
1310
|
-
*
|
|
1311
|
-
* @param key Step ID
|
|
1312
|
-
* @param value Step data
|
|
1313
|
-
* @param historyId Related history entry ID
|
|
1314
|
-
* @param agentId Agent ID for filtering
|
|
1381
|
+
* Get statistics about stored spans
|
|
1315
1382
|
*/
|
|
1316
|
-
async
|
|
1317
|
-
await this.
|
|
1383
|
+
async getStats() {
|
|
1384
|
+
await this.ensureInitialized();
|
|
1318
1385
|
try {
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1386
|
+
const [spanCountResult, traceCountResult, timeRangeResult] = await Promise.all([
|
|
1387
|
+
this.client.execute(`SELECT COUNT(*) as count FROM ${this.tablePrefix}_spans`),
|
|
1388
|
+
this.client.execute(`SELECT COUNT(*) as count FROM ${this.tablePrefix}_traces`),
|
|
1389
|
+
this.client.execute(`
|
|
1390
|
+
SELECT
|
|
1391
|
+
MIN(start_time) as oldest,
|
|
1392
|
+
MAX(start_time) as newest
|
|
1393
|
+
FROM ${this.tablePrefix}_spans
|
|
1394
|
+
`)
|
|
1395
|
+
]);
|
|
1396
|
+
const stats = {
|
|
1397
|
+
spanCount: spanCountResult.rows[0].count,
|
|
1398
|
+
traceCount: traceCountResult.rows[0].count
|
|
1399
|
+
};
|
|
1400
|
+
if (timeRangeResult.rows[0].oldest) {
|
|
1401
|
+
stats.oldestSpan = new Date(timeRangeResult.rows[0].oldest);
|
|
1402
|
+
}
|
|
1403
|
+
if (timeRangeResult.rows[0].newest) {
|
|
1404
|
+
stats.newestSpan = new Date(timeRangeResult.rows[0].newest);
|
|
1405
|
+
}
|
|
1406
|
+
return stats;
|
|
1326
1407
|
} catch (error) {
|
|
1327
|
-
this.
|
|
1328
|
-
throw
|
|
1408
|
+
this.logger.error("Failed to get stats", { error });
|
|
1409
|
+
throw error;
|
|
1329
1410
|
}
|
|
1330
1411
|
}
|
|
1331
1412
|
/**
|
|
1332
|
-
*
|
|
1333
|
-
* @param key Step ID
|
|
1334
|
-
* @param value Updated step data
|
|
1335
|
-
* @param historyId Related history entry ID
|
|
1336
|
-
* @param agentId Agent ID for filtering
|
|
1337
|
-
*/
|
|
1338
|
-
async updateHistoryStep(key, value, historyId, agentId) {
|
|
1339
|
-
return this.addHistoryStep(key, value, historyId, agentId);
|
|
1340
|
-
}
|
|
1341
|
-
/**
|
|
1342
|
-
* Add a timeline event
|
|
1343
|
-
* @param key Event ID (UUID)
|
|
1344
|
-
* @param value Timeline event data
|
|
1345
|
-
* @param historyId Related history entry ID
|
|
1346
|
-
* @param agentId Agent ID for filtering
|
|
1413
|
+
* Save a log record to the database
|
|
1347
1414
|
*/
|
|
1348
|
-
async
|
|
1349
|
-
await this.
|
|
1415
|
+
async saveLogRecord(logRecord) {
|
|
1416
|
+
await this.ensureInitialized();
|
|
1350
1417
|
try {
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1418
|
+
let timestamp;
|
|
1419
|
+
if (Array.isArray(logRecord.hrTime)) {
|
|
1420
|
+
const timeMs = logRecord.hrTime[0] * 1e3 + logRecord.hrTime[1] / 1e6;
|
|
1421
|
+
timestamp = new Date(timeMs).toISOString();
|
|
1422
|
+
} else if (logRecord.timestamp) {
|
|
1423
|
+
timestamp = typeof logRecord.timestamp === "string" ? logRecord.timestamp : new Date(logRecord.timestamp).toISOString();
|
|
1424
|
+
} else {
|
|
1425
|
+
timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1426
|
+
}
|
|
1427
|
+
const spanContext = logRecord.spanContext || {};
|
|
1428
|
+
const traceId = spanContext.traceId || null;
|
|
1429
|
+
const spanId = spanContext.spanId || null;
|
|
1430
|
+
const traceFlags = spanContext.traceFlags ?? null;
|
|
1431
|
+
const severityNumber = logRecord.severityNumber ?? null;
|
|
1432
|
+
const severityText = logRecord.severityText || null;
|
|
1433
|
+
const body = typeof logRecord.body === "string" ? logRecord.body : (0, import_utils.safeStringify)(logRecord.body);
|
|
1434
|
+
const attributes = logRecord.attributes ? (0, import_utils.safeStringify)(logRecord.attributes) : null;
|
|
1435
|
+
const resource = logRecord.resource?.attributes ? (0, import_utils.safeStringify)(logRecord.resource.attributes) : null;
|
|
1436
|
+
const instrumentationScope = logRecord.instrumentationLibrary || logRecord.instrumentationScope ? (0, import_utils.safeStringify)(logRecord.instrumentationLibrary || logRecord.instrumentationScope) : null;
|
|
1357
1437
|
await this.client.execute({
|
|
1358
|
-
sql: `
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1438
|
+
sql: `
|
|
1439
|
+
INSERT INTO ${this.tablePrefix}_logs (
|
|
1440
|
+
timestamp, trace_id, span_id, trace_flags,
|
|
1441
|
+
severity_number, severity_text, body,
|
|
1442
|
+
attributes, resource, instrumentation_scope
|
|
1443
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1444
|
+
`,
|
|
1364
1445
|
args: [
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
value.version || null,
|
|
1376
|
-
value.parentEventId || null,
|
|
1377
|
-
tagsJSON,
|
|
1378
|
-
inputJSON,
|
|
1379
|
-
outputJSON,
|
|
1380
|
-
statusMessageJSON,
|
|
1381
|
-
metadataJSON
|
|
1446
|
+
timestamp,
|
|
1447
|
+
traceId,
|
|
1448
|
+
spanId,
|
|
1449
|
+
traceFlags,
|
|
1450
|
+
severityNumber,
|
|
1451
|
+
severityText,
|
|
1452
|
+
body,
|
|
1453
|
+
attributes,
|
|
1454
|
+
resource,
|
|
1455
|
+
instrumentationScope
|
|
1382
1456
|
]
|
|
1383
1457
|
});
|
|
1384
|
-
this.
|
|
1458
|
+
this.debugLog("Log record saved successfully", {
|
|
1459
|
+
timestamp,
|
|
1460
|
+
traceId,
|
|
1461
|
+
spanId,
|
|
1462
|
+
severityNumber
|
|
1463
|
+
});
|
|
1385
1464
|
} catch (error) {
|
|
1386
|
-
this.
|
|
1387
|
-
throw
|
|
1465
|
+
this.logger.error("Failed to save log record", { error, logRecord });
|
|
1466
|
+
throw error;
|
|
1388
1467
|
}
|
|
1389
1468
|
}
|
|
1390
1469
|
/**
|
|
1391
|
-
* Get
|
|
1392
|
-
* @param key Entry ID
|
|
1393
|
-
* @returns The history entry or undefined if not found
|
|
1470
|
+
* Get logs by trace ID
|
|
1394
1471
|
*/
|
|
1395
|
-
async
|
|
1396
|
-
await this.
|
|
1472
|
+
async getLogsByTraceId(traceId) {
|
|
1473
|
+
await this.ensureInitialized();
|
|
1397
1474
|
try {
|
|
1398
|
-
const tableName = `${this.options.tablePrefix}_agent_history`;
|
|
1399
1475
|
const result = await this.client.execute({
|
|
1400
|
-
sql: `
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1408
|
-
const row = result.rows[0];
|
|
1409
|
-
const entry = {
|
|
1410
|
-
id: row.id,
|
|
1411
|
-
_agentId: row.agent_id,
|
|
1412
|
-
// Keep _agentId for compatibility
|
|
1413
|
-
timestamp: new Date(row.timestamp),
|
|
1414
|
-
status: row.status,
|
|
1415
|
-
input: row.input ? (0, import_core.safeJsonParse)(row.input) : null,
|
|
1416
|
-
output: row.output ? (0, import_core.safeJsonParse)(row.output) : null,
|
|
1417
|
-
usage: row.usage ? (0, import_core.safeJsonParse)(row.usage) : null,
|
|
1418
|
-
metadata: row.metadata ? (0, import_core.safeJsonParse)(row.metadata) : null,
|
|
1419
|
-
userId: row.userId,
|
|
1420
|
-
conversationId: row.conversationId
|
|
1421
|
-
};
|
|
1422
|
-
this.debug(`Got history entry with ID ${key}`);
|
|
1423
|
-
const stepsTableName = `${this.options.tablePrefix}_agent_history_steps`;
|
|
1424
|
-
const stepsResult = await this.client.execute({
|
|
1425
|
-
sql: `SELECT value FROM ${stepsTableName} WHERE history_id = ? AND agent_id = ?`,
|
|
1426
|
-
args: [key, entry._agentId]
|
|
1427
|
-
});
|
|
1428
|
-
const steps = stepsResult.rows.map((row2) => {
|
|
1429
|
-
const step = (0, import_core.safeJsonParse)(row2.value);
|
|
1430
|
-
return {
|
|
1431
|
-
type: step.type,
|
|
1432
|
-
name: step.name,
|
|
1433
|
-
content: step.content,
|
|
1434
|
-
arguments: step.arguments
|
|
1435
|
-
};
|
|
1436
|
-
});
|
|
1437
|
-
const timelineEventsTableName = `${this.options.tablePrefix}_agent_history_timeline_events`;
|
|
1438
|
-
const timelineEventsResult = await this.client.execute({
|
|
1439
|
-
sql: `SELECT id, event_type, event_name, start_time, end_time,
|
|
1440
|
-
status, status_message, level, version,
|
|
1441
|
-
parent_event_id, tags, input, output, error, metadata
|
|
1442
|
-
FROM ${timelineEventsTableName}
|
|
1443
|
-
WHERE history_id = ? AND agent_id = ?`,
|
|
1444
|
-
args: [key, entry._agentId]
|
|
1445
|
-
});
|
|
1446
|
-
const events = timelineEventsResult.rows.map((row2) => {
|
|
1447
|
-
const input = row2.input ? (0, import_core.safeJsonParse)(row2.input) : void 0;
|
|
1448
|
-
const output = row2.output ? (0, import_core.safeJsonParse)(row2.output) : void 0;
|
|
1449
|
-
const error = row2.error ? (0, import_core.safeJsonParse)(row2.error) : void 0;
|
|
1450
|
-
const statusMessage = row2.status_message ? (0, import_core.safeJsonParse)(row2.status_message) : void 0;
|
|
1451
|
-
const metadata = row2.metadata ? (0, import_core.safeJsonParse)(row2.metadata) : void 0;
|
|
1452
|
-
const tags = row2.tags ? (0, import_core.safeJsonParse)(row2.tags) : void 0;
|
|
1453
|
-
return {
|
|
1454
|
-
id: row2.id,
|
|
1455
|
-
type: row2.event_type,
|
|
1456
|
-
name: row2.event_name,
|
|
1457
|
-
startTime: row2.start_time,
|
|
1458
|
-
endTime: row2.end_time,
|
|
1459
|
-
status: row2.status,
|
|
1460
|
-
statusMessage,
|
|
1461
|
-
level: row2.level,
|
|
1462
|
-
version: row2.version,
|
|
1463
|
-
parentEventId: row2.parent_event_id,
|
|
1464
|
-
tags,
|
|
1465
|
-
input,
|
|
1466
|
-
output,
|
|
1467
|
-
error: statusMessage ? statusMessage : error,
|
|
1468
|
-
metadata
|
|
1469
|
-
};
|
|
1476
|
+
sql: `
|
|
1477
|
+
SELECT * FROM ${this.tablePrefix}_logs
|
|
1478
|
+
WHERE trace_id = ?
|
|
1479
|
+
ORDER BY timestamp DESC
|
|
1480
|
+
LIMIT ?
|
|
1481
|
+
`,
|
|
1482
|
+
args: [traceId, this.maxSpansPerQuery]
|
|
1470
1483
|
});
|
|
1471
|
-
|
|
1472
|
-
entry.events = events;
|
|
1473
|
-
return entry;
|
|
1484
|
+
return result.rows.map((row) => this.rowToLogRecord(row));
|
|
1474
1485
|
} catch (error) {
|
|
1475
|
-
this.
|
|
1476
|
-
|
|
1486
|
+
this.logger.error("Failed to get logs by trace ID", { error, traceId });
|
|
1487
|
+
throw error;
|
|
1477
1488
|
}
|
|
1478
1489
|
}
|
|
1479
1490
|
/**
|
|
1480
|
-
* Get
|
|
1481
|
-
* @param key Step ID
|
|
1482
|
-
* @returns The history step or undefined if not found
|
|
1491
|
+
* Get logs by span ID
|
|
1483
1492
|
*/
|
|
1484
|
-
async
|
|
1485
|
-
await this.
|
|
1486
|
-
try {
|
|
1487
|
-
const tableName = `${this.options.tablePrefix}_agent_history_steps`;
|
|
1488
|
-
const result = await this.client.execute({
|
|
1489
|
-
sql: `SELECT value FROM ${tableName} WHERE key = ?`,
|
|
1490
|
-
args: [key]
|
|
1491
|
-
});
|
|
1492
|
-
if (result.rows.length === 0) {
|
|
1493
|
-
this.debug(`History step with ID ${key} not found`);
|
|
1494
|
-
return void 0;
|
|
1495
|
-
}
|
|
1496
|
-
const value = (0, import_core.safeJsonParse)(result.rows[0].value);
|
|
1497
|
-
this.debug(`Got history step with ID ${key}`);
|
|
1498
|
-
return value;
|
|
1499
|
-
} catch (error) {
|
|
1500
|
-
this.debug(`Error getting history step with ID ${key}`, error);
|
|
1501
|
-
return void 0;
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
async createConversation(conversation) {
|
|
1505
|
-
await this.initialized;
|
|
1506
|
-
await debugDelay();
|
|
1507
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1508
|
-
const metadataString = (0, import_utils2.safeStringify)(conversation.metadata);
|
|
1509
|
-
const tableName = `${this.options.tablePrefix}_conversations`;
|
|
1510
|
-
return await this.executeWithRetryStrategy(async () => {
|
|
1511
|
-
await this.client.execute({
|
|
1512
|
-
sql: `INSERT INTO ${tableName} (id, resource_id, user_id, title, metadata, created_at, updated_at)
|
|
1513
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
1514
|
-
args: [
|
|
1515
|
-
conversation.id,
|
|
1516
|
-
conversation.resourceId,
|
|
1517
|
-
conversation.userId,
|
|
1518
|
-
conversation.title,
|
|
1519
|
-
metadataString,
|
|
1520
|
-
now,
|
|
1521
|
-
now
|
|
1522
|
-
]
|
|
1523
|
-
});
|
|
1524
|
-
return {
|
|
1525
|
-
id: conversation.id,
|
|
1526
|
-
resourceId: conversation.resourceId,
|
|
1527
|
-
userId: conversation.userId,
|
|
1528
|
-
title: conversation.title,
|
|
1529
|
-
metadata: conversation.metadata,
|
|
1530
|
-
createdAt: now,
|
|
1531
|
-
updatedAt: now
|
|
1532
|
-
};
|
|
1533
|
-
}, `createConversation[${conversation.id}]`);
|
|
1534
|
-
}
|
|
1535
|
-
async getConversation(id) {
|
|
1536
|
-
await this.initialized;
|
|
1537
|
-
await debugDelay();
|
|
1538
|
-
const tableName = `${this.options.tablePrefix}_conversations`;
|
|
1539
|
-
try {
|
|
1540
|
-
const result = await this.client.execute({
|
|
1541
|
-
sql: `SELECT * FROM ${tableName} WHERE id = ?`,
|
|
1542
|
-
args: [id]
|
|
1543
|
-
});
|
|
1544
|
-
if (result.rows.length === 0) {
|
|
1545
|
-
return null;
|
|
1546
|
-
}
|
|
1547
|
-
const row = result.rows[0];
|
|
1548
|
-
return {
|
|
1549
|
-
id: row.id,
|
|
1550
|
-
resourceId: row.resource_id,
|
|
1551
|
-
userId: row.user_id,
|
|
1552
|
-
title: row.title,
|
|
1553
|
-
metadata: row.metadata ? (0, import_core.safeJsonParse)(row.metadata) : {},
|
|
1554
|
-
createdAt: row.created_at,
|
|
1555
|
-
updatedAt: row.updated_at
|
|
1556
|
-
};
|
|
1557
|
-
} catch (error) {
|
|
1558
|
-
this.debug("Error getting conversation:", error);
|
|
1559
|
-
throw new Error("Failed to get conversation from LibSQL database");
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
async getConversations(resourceId) {
|
|
1563
|
-
await this.initialized;
|
|
1564
|
-
await debugDelay();
|
|
1565
|
-
const tableName = `${this.options.tablePrefix}_conversations`;
|
|
1566
|
-
try {
|
|
1567
|
-
const result = await this.client.execute({
|
|
1568
|
-
sql: `SELECT * FROM ${tableName} WHERE resource_id = ? ORDER BY updated_at DESC`,
|
|
1569
|
-
args: [resourceId]
|
|
1570
|
-
});
|
|
1571
|
-
return result.rows.map((row) => ({
|
|
1572
|
-
id: row.id,
|
|
1573
|
-
resourceId: row.resource_id,
|
|
1574
|
-
userId: row.user_id,
|
|
1575
|
-
title: row.title,
|
|
1576
|
-
metadata: (0, import_core.safeJsonParse)(row.metadata),
|
|
1577
|
-
createdAt: row.created_at,
|
|
1578
|
-
updatedAt: row.updated_at
|
|
1579
|
-
}));
|
|
1580
|
-
} catch (error) {
|
|
1581
|
-
this.debug("Error getting conversations:", error);
|
|
1582
|
-
throw new Error("Failed to get conversations from LibSQL database");
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
async getConversationsByUserId(userId, options = {}) {
|
|
1586
|
-
await this.initialized;
|
|
1587
|
-
await debugDelay();
|
|
1588
|
-
const {
|
|
1589
|
-
resourceId,
|
|
1590
|
-
limit = 50,
|
|
1591
|
-
offset = 0,
|
|
1592
|
-
orderBy = "updated_at",
|
|
1593
|
-
orderDirection = "DESC"
|
|
1594
|
-
} = options;
|
|
1595
|
-
const tableName = `${this.options.tablePrefix}_conversations`;
|
|
1493
|
+
async getLogsBySpanId(spanId) {
|
|
1494
|
+
await this.ensureInitialized();
|
|
1596
1495
|
try {
|
|
1597
|
-
let sql = `SELECT * FROM ${tableName} WHERE user_id = ?`;
|
|
1598
|
-
const args = [userId];
|
|
1599
|
-
if (resourceId) {
|
|
1600
|
-
sql += " AND resource_id = ?";
|
|
1601
|
-
args.push(resourceId);
|
|
1602
|
-
}
|
|
1603
|
-
sql += ` ORDER BY ${orderBy} ${orderDirection}`;
|
|
1604
|
-
if (limit > 0) {
|
|
1605
|
-
sql += " LIMIT ? OFFSET ?";
|
|
1606
|
-
args.push(limit, offset);
|
|
1607
|
-
}
|
|
1608
1496
|
const result = await this.client.execute({
|
|
1609
|
-
sql
|
|
1610
|
-
|
|
1497
|
+
sql: `
|
|
1498
|
+
SELECT * FROM ${this.tablePrefix}_logs
|
|
1499
|
+
WHERE span_id = ?
|
|
1500
|
+
ORDER BY timestamp DESC
|
|
1501
|
+
LIMIT ?
|
|
1502
|
+
`,
|
|
1503
|
+
args: [spanId, this.maxSpansPerQuery]
|
|
1611
1504
|
});
|
|
1612
|
-
return result.rows.map((row) => (
|
|
1613
|
-
id: row.id,
|
|
1614
|
-
resourceId: row.resource_id,
|
|
1615
|
-
userId: row.user_id,
|
|
1616
|
-
title: row.title,
|
|
1617
|
-
metadata: (0, import_core.safeJsonParse)(row.metadata),
|
|
1618
|
-
createdAt: row.created_at,
|
|
1619
|
-
updatedAt: row.updated_at
|
|
1620
|
-
}));
|
|
1505
|
+
return result.rows.map((row) => this.rowToLogRecord(row));
|
|
1621
1506
|
} catch (error) {
|
|
1622
|
-
this.
|
|
1623
|
-
throw
|
|
1507
|
+
this.logger.error("Failed to get logs by span ID", { error, spanId });
|
|
1508
|
+
throw error;
|
|
1624
1509
|
}
|
|
1625
1510
|
}
|
|
1626
1511
|
/**
|
|
1627
|
-
* Query
|
|
1628
|
-
*
|
|
1629
|
-
* @param options Query options for filtering and pagination
|
|
1630
|
-
* @returns Promise that resolves to an array of conversations matching the criteria
|
|
1631
|
-
* @see {@link https://voltagent.dev/docs/agents/memory/libsql#querying-conversations | Querying Conversations}
|
|
1512
|
+
* Query logs with flexible filtering
|
|
1632
1513
|
*/
|
|
1633
|
-
async
|
|
1634
|
-
await this.
|
|
1635
|
-
await debugDelay();
|
|
1636
|
-
const {
|
|
1637
|
-
userId,
|
|
1638
|
-
resourceId,
|
|
1639
|
-
limit = 50,
|
|
1640
|
-
offset = 0,
|
|
1641
|
-
orderBy = "updated_at",
|
|
1642
|
-
orderDirection = "DESC"
|
|
1643
|
-
} = options;
|
|
1644
|
-
const tableName = `${this.options.tablePrefix}_conversations`;
|
|
1514
|
+
async queryLogs(filter) {
|
|
1515
|
+
await this.ensureInitialized();
|
|
1645
1516
|
try {
|
|
1646
|
-
|
|
1517
|
+
const whereClauses = [];
|
|
1647
1518
|
const args = [];
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
args.push(userId);
|
|
1519
|
+
if (filter.traceId) {
|
|
1520
|
+
whereClauses.push("trace_id = ?");
|
|
1521
|
+
args.push(filter.traceId);
|
|
1652
1522
|
}
|
|
1653
|
-
if (
|
|
1654
|
-
|
|
1655
|
-
args.push(
|
|
1523
|
+
if (filter.spanId) {
|
|
1524
|
+
whereClauses.push("span_id = ?");
|
|
1525
|
+
args.push(filter.spanId);
|
|
1656
1526
|
}
|
|
1657
|
-
if (
|
|
1658
|
-
|
|
1527
|
+
if (filter.severityNumber !== void 0) {
|
|
1528
|
+
whereClauses.push("severity_number >= ?");
|
|
1529
|
+
args.push(filter.severityNumber);
|
|
1659
1530
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
args.push(limit, offset);
|
|
1531
|
+
if (filter.severityText) {
|
|
1532
|
+
whereClauses.push("severity_text = ?");
|
|
1533
|
+
args.push(filter.severityText);
|
|
1664
1534
|
}
|
|
1535
|
+
if (filter.instrumentationScope) {
|
|
1536
|
+
whereClauses.push("instrumentation_scope LIKE ?");
|
|
1537
|
+
args.push(`%${filter.instrumentationScope}%`);
|
|
1538
|
+
}
|
|
1539
|
+
if (filter.startTimeMin !== void 0) {
|
|
1540
|
+
const minTime = new Date(filter.startTimeMin).toISOString();
|
|
1541
|
+
whereClauses.push("timestamp >= ?");
|
|
1542
|
+
args.push(minTime);
|
|
1543
|
+
}
|
|
1544
|
+
if (filter.startTimeMax !== void 0) {
|
|
1545
|
+
const maxTime = new Date(filter.startTimeMax).toISOString();
|
|
1546
|
+
whereClauses.push("timestamp <= ?");
|
|
1547
|
+
args.push(maxTime);
|
|
1548
|
+
}
|
|
1549
|
+
if (filter.bodyContains) {
|
|
1550
|
+
whereClauses.push("body LIKE ?");
|
|
1551
|
+
args.push(`%${filter.bodyContains}%`);
|
|
1552
|
+
}
|
|
1553
|
+
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
1554
|
+
const limit = filter.limit || this.maxSpansPerQuery;
|
|
1555
|
+
args.push(limit);
|
|
1665
1556
|
const result = await this.client.execute({
|
|
1666
|
-
sql
|
|
1557
|
+
sql: `
|
|
1558
|
+
SELECT * FROM ${this.tablePrefix}_logs
|
|
1559
|
+
${whereClause}
|
|
1560
|
+
ORDER BY timestamp DESC
|
|
1561
|
+
LIMIT ?
|
|
1562
|
+
`,
|
|
1667
1563
|
args
|
|
1668
1564
|
});
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1565
|
+
const logs = result.rows.map((row) => this.rowToLogRecord(row));
|
|
1566
|
+
if (filter.attributeKey) {
|
|
1567
|
+
const key = filter.attributeKey;
|
|
1568
|
+
return logs.filter((log) => {
|
|
1569
|
+
if (!log.attributes) return false;
|
|
1570
|
+
if (filter.attributeValue !== void 0) {
|
|
1571
|
+
return log.attributes[key] === filter.attributeValue;
|
|
1572
|
+
}
|
|
1573
|
+
return key in log.attributes;
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
return logs;
|
|
1678
1577
|
} catch (error) {
|
|
1679
|
-
this.
|
|
1680
|
-
throw
|
|
1578
|
+
this.logger.error("Failed to query logs", { error, filter });
|
|
1579
|
+
throw error;
|
|
1681
1580
|
}
|
|
1682
1581
|
}
|
|
1683
1582
|
/**
|
|
1684
|
-
*
|
|
1685
|
-
*
|
|
1686
|
-
* @param conversationId The unique identifier of the conversation to retrieve messages from
|
|
1687
|
-
* @param options Optional pagination and filtering options
|
|
1688
|
-
* @returns Promise that resolves to an array of messages in chronological order (oldest first)
|
|
1689
|
-
* @see {@link https://voltagent.dev/docs/agents/memory/libsql#conversation-messages | Getting Conversation Messages}
|
|
1583
|
+
* Delete old logs
|
|
1690
1584
|
*/
|
|
1691
|
-
async
|
|
1692
|
-
await this.
|
|
1693
|
-
await debugDelay();
|
|
1694
|
-
const { limit = 100, offset = 0 } = options;
|
|
1695
|
-
const tableName = `${this.options.tablePrefix}_messages`;
|
|
1585
|
+
async deleteOldLogs(beforeTimestamp) {
|
|
1586
|
+
await this.ensureInitialized();
|
|
1696
1587
|
try {
|
|
1697
|
-
|
|
1698
|
-
const args = [conversationId];
|
|
1699
|
-
if (limit > 0) {
|
|
1700
|
-
sql += " LIMIT ? OFFSET ?";
|
|
1701
|
-
args.push(limit, offset);
|
|
1702
|
-
}
|
|
1588
|
+
const beforeDate = new Date(beforeTimestamp).toISOString();
|
|
1703
1589
|
const result = await this.client.execute({
|
|
1704
|
-
sql
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
const parsedContent = (0, import_core.safeJsonParse)(content);
|
|
1710
|
-
if (parsedContent !== null) {
|
|
1711
|
-
content = parsedContent;
|
|
1712
|
-
}
|
|
1713
|
-
return {
|
|
1714
|
-
id: row.message_id,
|
|
1715
|
-
role: row.role,
|
|
1716
|
-
content,
|
|
1717
|
-
type: row.type,
|
|
1718
|
-
createdAt: row.created_at
|
|
1719
|
-
};
|
|
1590
|
+
sql: `
|
|
1591
|
+
DELETE FROM ${this.tablePrefix}_logs
|
|
1592
|
+
WHERE timestamp < ?
|
|
1593
|
+
`,
|
|
1594
|
+
args: [beforeDate]
|
|
1720
1595
|
});
|
|
1596
|
+
const deletedCount = result.rowsAffected || 0;
|
|
1597
|
+
this.debugLog("Old logs deleted", { deletedCount, beforeDate });
|
|
1598
|
+
return deletedCount;
|
|
1721
1599
|
} catch (error) {
|
|
1722
|
-
this.
|
|
1723
|
-
throw
|
|
1600
|
+
this.logger.error("Failed to delete old logs", { error, beforeTimestamp });
|
|
1601
|
+
throw error;
|
|
1724
1602
|
}
|
|
1725
1603
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1604
|
+
/**
|
|
1605
|
+
* Convert a database row to an ObservabilityLogRecord
|
|
1606
|
+
*/
|
|
1607
|
+
rowToLogRecord(row) {
|
|
1608
|
+
const log = {
|
|
1609
|
+
timestamp: row.timestamp,
|
|
1610
|
+
body: (() => {
|
|
1611
|
+
try {
|
|
1612
|
+
const bodyStr = row.body;
|
|
1613
|
+
if (bodyStr.startsWith("{") || bodyStr.startsWith("[")) {
|
|
1614
|
+
return JSON.parse(bodyStr);
|
|
1615
|
+
}
|
|
1616
|
+
} catch {
|
|
1617
|
+
}
|
|
1618
|
+
return row.body;
|
|
1619
|
+
})()
|
|
1620
|
+
};
|
|
1621
|
+
if (row.trace_id !== null) {
|
|
1622
|
+
log.traceId = row.trace_id;
|
|
1623
|
+
}
|
|
1624
|
+
if (row.span_id !== null) {
|
|
1625
|
+
log.spanId = row.span_id;
|
|
1626
|
+
}
|
|
1627
|
+
if (row.trace_flags !== null) {
|
|
1628
|
+
log.traceFlags = row.trace_flags;
|
|
1629
|
+
}
|
|
1630
|
+
if (row.severity_number !== null) {
|
|
1631
|
+
log.severityNumber = row.severity_number;
|
|
1632
|
+
}
|
|
1633
|
+
if (row.severity_text !== null) {
|
|
1634
|
+
log.severityText = row.severity_text;
|
|
1635
|
+
}
|
|
1636
|
+
if (row.attributes && row.attributes !== "null") {
|
|
1637
|
+
try {
|
|
1638
|
+
const attributes = JSON.parse(row.attributes);
|
|
1639
|
+
if (attributes && Object.keys(attributes).length > 0) {
|
|
1640
|
+
log.attributes = attributes;
|
|
1641
|
+
}
|
|
1642
|
+
} catch {
|
|
1741
1643
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1644
|
+
}
|
|
1645
|
+
if (row.resource && row.resource !== "null") {
|
|
1646
|
+
try {
|
|
1647
|
+
const resource = JSON.parse(row.resource);
|
|
1648
|
+
if (resource && Object.keys(resource).length > 0) {
|
|
1649
|
+
log.resource = resource;
|
|
1650
|
+
}
|
|
1651
|
+
} catch {
|
|
1745
1652
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1653
|
+
}
|
|
1654
|
+
if (row.instrumentation_scope && row.instrumentation_scope !== "null") {
|
|
1655
|
+
try {
|
|
1656
|
+
const scope = JSON.parse(row.instrumentation_scope);
|
|
1657
|
+
if (scope) {
|
|
1658
|
+
log.instrumentationScope = scope;
|
|
1659
|
+
}
|
|
1660
|
+
} catch {
|
|
1749
1661
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1662
|
+
}
|
|
1663
|
+
return log;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Close the database connection
|
|
1667
|
+
*/
|
|
1668
|
+
async close() {
|
|
1669
|
+
this.debugLog("LibSQL observability adapter closed");
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
// src/vector-adapter.ts
|
|
1674
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
1675
|
+
var import_node_path3 = __toESM(require("path"));
|
|
1676
|
+
var import_client3 = require("@libsql/client");
|
|
1677
|
+
var import_core2 = require("@voltagent/core");
|
|
1678
|
+
var import_logger3 = require("@voltagent/logger");
|
|
1679
|
+
var LibSQLVectorAdapter = class {
|
|
1680
|
+
static {
|
|
1681
|
+
__name(this, "LibSQLVectorAdapter");
|
|
1682
|
+
}
|
|
1683
|
+
client;
|
|
1684
|
+
tablePrefix;
|
|
1685
|
+
maxVectorDimensions;
|
|
1686
|
+
cacheSize;
|
|
1687
|
+
batchSize;
|
|
1688
|
+
debug;
|
|
1689
|
+
logger;
|
|
1690
|
+
maxRetries;
|
|
1691
|
+
retryDelayMs;
|
|
1692
|
+
url;
|
|
1693
|
+
initialized = false;
|
|
1694
|
+
vectorCache;
|
|
1695
|
+
dimensions = null;
|
|
1696
|
+
constructor(options = {}) {
|
|
1697
|
+
this.tablePrefix = options.tablePrefix ?? "voltagent";
|
|
1698
|
+
this.maxVectorDimensions = options.maxVectorDimensions ?? 1536;
|
|
1699
|
+
this.cacheSize = options.cacheSize ?? 100;
|
|
1700
|
+
this.batchSize = options.batchSize ?? 100;
|
|
1701
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
1702
|
+
this.retryDelayMs = options.retryDelayMs ?? 100;
|
|
1703
|
+
this.debug = options.debug ?? false;
|
|
1704
|
+
this.logger = options.logger ?? (0, import_logger3.createPinoLogger)({
|
|
1705
|
+
name: "libsql-vector-adapter",
|
|
1706
|
+
level: this.debug ? "debug" : "info"
|
|
1707
|
+
});
|
|
1708
|
+
const requestedUrl = options.url ?? "file:./.voltagent/memory.db";
|
|
1709
|
+
if (requestedUrl === ":memory:" || requestedUrl === "file::memory:" || requestedUrl.startsWith("file::memory:")) {
|
|
1710
|
+
this.url = ":memory:";
|
|
1711
|
+
} else {
|
|
1712
|
+
this.url = requestedUrl;
|
|
1713
|
+
}
|
|
1714
|
+
if (this.url.startsWith("file:") && !this.url.startsWith("file::memory:")) {
|
|
1715
|
+
const dbPath = this.url.replace("file:", "");
|
|
1716
|
+
const dbDir = import_node_path3.default.dirname(dbPath);
|
|
1717
|
+
if (!import_node_fs3.default.existsSync(dbDir)) {
|
|
1718
|
+
import_node_fs3.default.mkdirSync(dbDir, { recursive: true });
|
|
1760
1719
|
}
|
|
1761
|
-
return updated;
|
|
1762
|
-
} catch (error) {
|
|
1763
|
-
this.debug("Error updating conversation:", error);
|
|
1764
|
-
throw new Error("Failed to update conversation in LibSQL database");
|
|
1765
1720
|
}
|
|
1721
|
+
this.client = (0, import_client3.createClient)({
|
|
1722
|
+
url: this.url,
|
|
1723
|
+
authToken: options.authToken
|
|
1724
|
+
});
|
|
1725
|
+
this.vectorCache = /* @__PURE__ */ new Map();
|
|
1766
1726
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1727
|
+
/**
|
|
1728
|
+
* Initialize the database schema
|
|
1729
|
+
*/
|
|
1730
|
+
async initialize() {
|
|
1731
|
+
if (this.initialized) return;
|
|
1732
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
1772
1733
|
try {
|
|
1773
|
-
await this.client.
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1734
|
+
await this.client.executeMultiple(`
|
|
1735
|
+
BEGIN;
|
|
1736
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
1737
|
+
id TEXT PRIMARY KEY,
|
|
1738
|
+
vector BLOB NOT NULL,
|
|
1739
|
+
dimensions INTEGER NOT NULL,
|
|
1740
|
+
metadata TEXT,
|
|
1741
|
+
content TEXT,
|
|
1742
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
1743
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
1744
|
+
);
|
|
1745
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_created ON ${tableName}(created_at);
|
|
1746
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_dimensions ON ${tableName}(dimensions);
|
|
1747
|
+
COMMIT;
|
|
1748
|
+
`);
|
|
1749
|
+
this.initialized = true;
|
|
1750
|
+
this.logger.debug("Vector adapter initialized");
|
|
1781
1751
|
} catch (error) {
|
|
1782
|
-
this.
|
|
1783
|
-
throw
|
|
1752
|
+
this.logger.error("Failed to initialize vector adapter", error);
|
|
1753
|
+
throw error;
|
|
1784
1754
|
}
|
|
1785
1755
|
}
|
|
1786
1756
|
/**
|
|
1787
|
-
*
|
|
1788
|
-
* @param agentId Agent ID
|
|
1789
|
-
* @param page Page number (0-based)
|
|
1790
|
-
* @param limit Number of entries per page
|
|
1791
|
-
* @returns Object with entries array and total count
|
|
1757
|
+
* Serialize a vector to binary format
|
|
1792
1758
|
*/
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
const offset = page * limit;
|
|
1798
|
-
const countResult = await this.client.execute({
|
|
1799
|
-
sql: `SELECT COUNT(*) as total FROM ${tableName} WHERE agent_id = ?`,
|
|
1800
|
-
args: [agentId]
|
|
1801
|
-
});
|
|
1802
|
-
const total = Number(countResult.rows[0].total);
|
|
1803
|
-
const result = await this.client.execute({
|
|
1804
|
-
sql: `SELECT id, agent_id, timestamp, status, input, output, usage, metadata, userId, conversationId
|
|
1805
|
-
FROM ${tableName} WHERE agent_id = ?
|
|
1806
|
-
ORDER BY timestamp DESC
|
|
1807
|
-
LIMIT ? OFFSET ?`,
|
|
1808
|
-
args: [agentId, limit, offset]
|
|
1809
|
-
});
|
|
1810
|
-
const entries = result.rows.map((row) => ({
|
|
1811
|
-
id: row.id,
|
|
1812
|
-
_agentId: row.agent_id,
|
|
1813
|
-
// Keep _agentId for compatibility
|
|
1814
|
-
timestamp: new Date(row.timestamp),
|
|
1815
|
-
status: row.status,
|
|
1816
|
-
input: row.input ? (0, import_core.safeJsonParse)(row.input) : null,
|
|
1817
|
-
output: row.output ? (0, import_core.safeJsonParse)(row.output) : null,
|
|
1818
|
-
usage: row.usage ? (0, import_core.safeJsonParse)(row.usage) : null,
|
|
1819
|
-
metadata: row.metadata ? (0, import_core.safeJsonParse)(row.metadata) : null,
|
|
1820
|
-
userId: row.userId,
|
|
1821
|
-
conversationId: row.conversationId
|
|
1822
|
-
}));
|
|
1823
|
-
this.debug(`Got all history entries for agent ${agentId} (${entries.length} items)`);
|
|
1824
|
-
const completeEntries = await Promise.all(
|
|
1825
|
-
entries.map(async (entry) => {
|
|
1826
|
-
const stepsTableName = `${this.options.tablePrefix}_agent_history_steps`;
|
|
1827
|
-
const stepsResult = await this.client.execute({
|
|
1828
|
-
sql: `SELECT value FROM ${stepsTableName} WHERE history_id = ? AND agent_id = ?`,
|
|
1829
|
-
args: [entry.id, agentId]
|
|
1830
|
-
});
|
|
1831
|
-
const steps = stepsResult.rows.map((row) => {
|
|
1832
|
-
const step = (0, import_core.safeJsonParse)(row.value);
|
|
1833
|
-
return {
|
|
1834
|
-
type: step.type,
|
|
1835
|
-
name: step.name,
|
|
1836
|
-
content: step.content,
|
|
1837
|
-
arguments: step.arguments
|
|
1838
|
-
};
|
|
1839
|
-
});
|
|
1840
|
-
const timelineEventsTableName = `${this.options.tablePrefix}_agent_history_timeline_events`;
|
|
1841
|
-
const timelineEventsResult = await this.client.execute({
|
|
1842
|
-
sql: `SELECT id, event_type, event_name, start_time, end_time,
|
|
1843
|
-
status, status_message, level, version,
|
|
1844
|
-
parent_event_id, tags, input, output, error, metadata
|
|
1845
|
-
FROM ${timelineEventsTableName}
|
|
1846
|
-
WHERE history_id = ? AND agent_id = ?`,
|
|
1847
|
-
args: [entry.id, agentId]
|
|
1848
|
-
});
|
|
1849
|
-
const events = timelineEventsResult.rows.map((row) => {
|
|
1850
|
-
const input = row.input ? (0, import_core.safeJsonParse)(row.input) : void 0;
|
|
1851
|
-
const output = row.output ? (0, import_core.safeJsonParse)(row.output) : void 0;
|
|
1852
|
-
const error = row.error ? (0, import_core.safeJsonParse)(row.error) : void 0;
|
|
1853
|
-
const statusMessage = row.status_message ? (0, import_core.safeJsonParse)(row.status_message) : void 0;
|
|
1854
|
-
const metadata = row.metadata ? (0, import_core.safeJsonParse)(row.metadata) : void 0;
|
|
1855
|
-
const tags = row.tags ? (0, import_core.safeJsonParse)(row.tags) : void 0;
|
|
1856
|
-
return {
|
|
1857
|
-
id: row.id,
|
|
1858
|
-
type: row.event_type,
|
|
1859
|
-
name: row.event_name,
|
|
1860
|
-
startTime: row.start_time,
|
|
1861
|
-
endTime: row.end_time,
|
|
1862
|
-
status: row.status,
|
|
1863
|
-
statusMessage,
|
|
1864
|
-
level: row.level,
|
|
1865
|
-
version: row.version,
|
|
1866
|
-
parentEventId: row.parent_event_id,
|
|
1867
|
-
tags,
|
|
1868
|
-
input,
|
|
1869
|
-
output,
|
|
1870
|
-
error: statusMessage ? statusMessage : error,
|
|
1871
|
-
metadata
|
|
1872
|
-
};
|
|
1873
|
-
});
|
|
1874
|
-
entry.steps = steps;
|
|
1875
|
-
entry.events = events;
|
|
1876
|
-
return entry;
|
|
1877
|
-
})
|
|
1878
|
-
);
|
|
1879
|
-
return {
|
|
1880
|
-
entries: completeEntries,
|
|
1881
|
-
total
|
|
1882
|
-
};
|
|
1883
|
-
} catch (error) {
|
|
1884
|
-
this.debug(`Error getting history entries for agent ${agentId}`, error);
|
|
1885
|
-
return {
|
|
1886
|
-
entries: [],
|
|
1887
|
-
total: 0
|
|
1888
|
-
};
|
|
1759
|
+
serializeVector(vector) {
|
|
1760
|
+
const buffer = Buffer.allocUnsafe(vector.length * 4);
|
|
1761
|
+
for (let i = 0; i < vector.length; i++) {
|
|
1762
|
+
buffer.writeFloatLE(vector[i], i * 4);
|
|
1889
1763
|
}
|
|
1764
|
+
return buffer;
|
|
1890
1765
|
}
|
|
1891
1766
|
/**
|
|
1892
|
-
*
|
|
1893
|
-
* If migration fails, it can be rolled back using the backup mechanism.
|
|
1894
|
-
*
|
|
1895
|
-
* Old database structure:
|
|
1896
|
-
* CREATE TABLE voltagent_memory_agent_history (
|
|
1897
|
-
* key TEXT PRIMARY KEY,
|
|
1898
|
-
* value TEXT NOT NULL,
|
|
1899
|
-
* agent_id TEXT
|
|
1900
|
-
* );
|
|
1767
|
+
* Deserialize a vector from binary format
|
|
1901
1768
|
*/
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
success: true,
|
|
1933
|
-
backupCreated: false
|
|
1934
|
-
};
|
|
1935
|
-
}
|
|
1936
|
-
const tableInfoQuery = await this.client.execute(`PRAGMA table_info(${oldTableName})`);
|
|
1937
|
-
if (tableInfoQuery.rows.length === 0) {
|
|
1938
|
-
this.debug(`${oldTableName} table not found, migration not needed`);
|
|
1939
|
-
return {
|
|
1940
|
-
success: true,
|
|
1941
|
-
migratedCount: 0
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
|
-
const hasValueColumn = tableInfoQuery.rows.some((row) => row.name === "value");
|
|
1945
|
-
if (!hasValueColumn) {
|
|
1946
|
-
this.debug("Table is already in new format, migration not needed");
|
|
1947
|
-
return {
|
|
1948
|
-
success: true,
|
|
1949
|
-
migratedCount: 0
|
|
1950
|
-
};
|
|
1951
|
-
}
|
|
1952
|
-
if (createBackup) {
|
|
1953
|
-
this.debug("Creating backup...");
|
|
1954
|
-
const backupCheck = await this.client.execute({
|
|
1955
|
-
sql: "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
1956
|
-
args: [oldTableBackup]
|
|
1957
|
-
});
|
|
1958
|
-
if (backupCheck.rows.length > 0) {
|
|
1959
|
-
await this.client.execute(`DROP TABLE IF EXISTS ${oldTableBackup};`);
|
|
1769
|
+
deserializeVector(buffer) {
|
|
1770
|
+
let bytes;
|
|
1771
|
+
if (buffer instanceof Buffer) {
|
|
1772
|
+
bytes = buffer;
|
|
1773
|
+
} else if (buffer instanceof ArrayBuffer) {
|
|
1774
|
+
bytes = Buffer.from(buffer);
|
|
1775
|
+
} else {
|
|
1776
|
+
bytes = Buffer.from(buffer);
|
|
1777
|
+
}
|
|
1778
|
+
const vector = [];
|
|
1779
|
+
for (let i = 0; i < bytes.length; i += 4) {
|
|
1780
|
+
vector.push(bytes.readFloatLE(i));
|
|
1781
|
+
}
|
|
1782
|
+
return vector;
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Execute a database operation with retries
|
|
1786
|
+
*/
|
|
1787
|
+
async executeWithRetry(operation, context) {
|
|
1788
|
+
let lastError;
|
|
1789
|
+
let delay = this.retryDelayMs;
|
|
1790
|
+
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
1791
|
+
try {
|
|
1792
|
+
return await operation();
|
|
1793
|
+
} catch (error) {
|
|
1794
|
+
lastError = error;
|
|
1795
|
+
this.logger.warn(`Operation failed (attempt ${attempt}): ${context}`, error);
|
|
1796
|
+
if (attempt < this.maxRetries) {
|
|
1797
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1798
|
+
delay *= 2;
|
|
1960
1799
|
}
|
|
1961
|
-
await this.client.execute(
|
|
1962
|
-
`CREATE TABLE ${oldTableBackup} AS SELECT * FROM ${oldTableName};`
|
|
1963
|
-
);
|
|
1964
|
-
this.debug("Backup created successfully");
|
|
1965
1800
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1801
|
+
}
|
|
1802
|
+
this.logger.error(`Operation failed after ${this.maxRetries} attempts: ${context}`, lastError);
|
|
1803
|
+
throw lastError;
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Store a vector with associated metadata
|
|
1807
|
+
*/
|
|
1808
|
+
async store(id, vector, metadata) {
|
|
1809
|
+
await this.initialize();
|
|
1810
|
+
if (!Array.isArray(vector) || vector.length === 0) {
|
|
1811
|
+
throw new Error("Vector must be a non-empty array");
|
|
1812
|
+
}
|
|
1813
|
+
if (vector.length > this.maxVectorDimensions) {
|
|
1814
|
+
throw new Error(
|
|
1815
|
+
`Vector dimensions (${vector.length}) exceed maximum (${this.maxVectorDimensions})`
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
if (this.dimensions === null) {
|
|
1819
|
+
this.dimensions = vector.length;
|
|
1820
|
+
} else if (vector.length !== this.dimensions) {
|
|
1821
|
+
throw new Error(
|
|
1822
|
+
`Vector dimension mismatch. Expected ${this.dimensions}, got ${vector.length}`
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
1826
|
+
const serializedVector = this.serializeVector(vector);
|
|
1827
|
+
const metadataJson = metadata ? JSON.stringify(metadata) : null;
|
|
1828
|
+
await this.executeWithRetry(async () => {
|
|
1829
|
+
await this.client.execute({
|
|
1830
|
+
sql: `
|
|
1831
|
+
INSERT OR REPLACE INTO ${tableName}
|
|
1832
|
+
(id, vector, dimensions, metadata, updated_at)
|
|
1833
|
+
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
1834
|
+
`,
|
|
1835
|
+
args: [id, serializedVector, vector.length, metadataJson]
|
|
1968
1836
|
});
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
)
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
let migratedCount = 0;
|
|
1992
|
-
const migratedIds = /* @__PURE__ */ new Set();
|
|
1993
|
-
for (const row of oldFormatData.rows) {
|
|
1994
|
-
const key = row.key;
|
|
1995
|
-
const agentId = row.agent_id;
|
|
1996
|
-
const valueStr = row.value;
|
|
1997
|
-
try {
|
|
1998
|
-
const valueObj = (0, import_core.safeJsonParse)(valueStr);
|
|
1999
|
-
const id = valueObj.id || key;
|
|
2000
|
-
if (migratedIds.has(id)) {
|
|
2001
|
-
continue;
|
|
1837
|
+
}, `store vector ${id}`);
|
|
1838
|
+
if (this.vectorCache.size >= this.cacheSize) {
|
|
1839
|
+
const firstKey = this.vectorCache.keys().next().value;
|
|
1840
|
+
if (firstKey) this.vectorCache.delete(firstKey);
|
|
1841
|
+
}
|
|
1842
|
+
this.vectorCache.set(id, { id, vector, metadata });
|
|
1843
|
+
this.logger.debug(`Vector stored: ${id} (${vector.length} dimensions)`);
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Store multiple vectors in batch
|
|
1847
|
+
*/
|
|
1848
|
+
async storeBatch(items) {
|
|
1849
|
+
await this.initialize();
|
|
1850
|
+
if (items.length === 0) return;
|
|
1851
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
1852
|
+
for (let i = 0; i < items.length; i += this.batchSize) {
|
|
1853
|
+
const batch = items.slice(i, i + this.batchSize);
|
|
1854
|
+
await this.executeWithRetry(async () => {
|
|
1855
|
+
const stmts = [];
|
|
1856
|
+
for (const item of batch) {
|
|
1857
|
+
if (!Array.isArray(item.vector) || item.vector.length === 0) {
|
|
1858
|
+
throw new Error("Vector must be a non-empty array");
|
|
2002
1859
|
}
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
sql: `INSERT INTO ${tempTableName}
|
|
2010
|
-
(id, agent_id, timestamp, status, input, output, usage, metadata)
|
|
2011
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2012
|
-
args: [
|
|
2013
|
-
id,
|
|
2014
|
-
valueObj._agentId || agentId,
|
|
2015
|
-
valueObj.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2016
|
-
valueObj.status || null,
|
|
2017
|
-
inputJSON,
|
|
2018
|
-
outputJSON,
|
|
2019
|
-
usageJSON,
|
|
2020
|
-
null
|
|
2021
|
-
]
|
|
2022
|
-
});
|
|
2023
|
-
let input = "";
|
|
2024
|
-
if (Array.isArray(valueObj.events)) {
|
|
2025
|
-
for (const event of valueObj.events) {
|
|
2026
|
-
try {
|
|
2027
|
-
if (event.affectedNodeId?.startsWith("message_")) {
|
|
2028
|
-
input = event.data.input;
|
|
2029
|
-
continue;
|
|
2030
|
-
}
|
|
2031
|
-
const eventId = event.id || this.generateId();
|
|
2032
|
-
const eventType = event.type || "unknown";
|
|
2033
|
-
let eventName = event.name || "unknown";
|
|
2034
|
-
const startTime = event.timestamp || event.startTime || (/* @__PURE__ */ new Date()).toISOString();
|
|
2035
|
-
const endTime = event.updatedAt || event.endTime || startTime;
|
|
2036
|
-
let status = event.status || event.data?.status || null;
|
|
2037
|
-
let inputData = null;
|
|
2038
|
-
if (event.input) {
|
|
2039
|
-
inputData = (0, import_utils2.safeStringify)({ input: event.input });
|
|
2040
|
-
} else if (event.data?.input) {
|
|
2041
|
-
inputData = (0, import_utils2.safeStringify)({ input: event.data.input });
|
|
2042
|
-
} else if (input) {
|
|
2043
|
-
inputData = (0, import_utils2.safeStringify)({ input });
|
|
2044
|
-
}
|
|
2045
|
-
input = "";
|
|
2046
|
-
let metadata = null;
|
|
2047
|
-
if (event.metadata) {
|
|
2048
|
-
metadata = (0, import_utils2.safeStringify)(event.metadata);
|
|
2049
|
-
} else if (event.data) {
|
|
2050
|
-
metadata = (0, import_utils2.safeStringify)({
|
|
2051
|
-
id: event.affectedNodeId?.split("_").pop(),
|
|
2052
|
-
agentId: event.data?.metadata?.sourceAgentId,
|
|
2053
|
-
...event.data
|
|
2054
|
-
});
|
|
2055
|
-
}
|
|
2056
|
-
if (eventType === "agent") {
|
|
2057
|
-
if (eventName === "start") {
|
|
2058
|
-
eventName = "agent:start";
|
|
2059
|
-
status = "running";
|
|
2060
|
-
} else if (eventName === "finished") {
|
|
2061
|
-
if (event.data.status === "error") {
|
|
2062
|
-
eventName = "agent:error";
|
|
2063
|
-
} else {
|
|
2064
|
-
eventName = "agent:success";
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
await this.client.execute({
|
|
2068
|
-
sql: `INSERT OR REPLACE INTO ${timelineEventsTableName}
|
|
2069
|
-
(id, history_id, agent_id, event_type, event_name, start_time, end_time,
|
|
2070
|
-
status, status_message, level, version, parent_event_id,
|
|
2071
|
-
tags, input, output, error, metadata)
|
|
2072
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2073
|
-
args: [
|
|
2074
|
-
eventId,
|
|
2075
|
-
id,
|
|
2076
|
-
valueObj._agentId || agentId,
|
|
2077
|
-
eventType,
|
|
2078
|
-
eventName,
|
|
2079
|
-
startTime,
|
|
2080
|
-
endTime,
|
|
2081
|
-
// @ts-ignore
|
|
2082
|
-
status,
|
|
2083
|
-
eventName === "agent:error" ? event.data.error.message : null,
|
|
2084
|
-
event.level || "INFO",
|
|
2085
|
-
event.version || null,
|
|
2086
|
-
event.parentEventId || null,
|
|
2087
|
-
null,
|
|
2088
|
-
// tags
|
|
2089
|
-
inputData,
|
|
2090
|
-
event.data.output ? (0, import_utils2.safeStringify)(event.data.output) : null,
|
|
2091
|
-
eventName === "agent:error" ? (0, import_utils2.safeStringify)(event.data.error) : null,
|
|
2092
|
-
metadata
|
|
2093
|
-
]
|
|
2094
|
-
});
|
|
2095
|
-
} else if (eventType === "memory") {
|
|
2096
|
-
if (eventName === "memory:saveMessage") {
|
|
2097
|
-
await this.client.execute({
|
|
2098
|
-
sql: `INSERT OR REPLACE INTO ${timelineEventsTableName}
|
|
2099
|
-
(id, history_id, agent_id, event_type, event_name, start_time, end_time,
|
|
2100
|
-
status, status_message, level, version, parent_event_id,
|
|
2101
|
-
tags, input, output, error, metadata)
|
|
2102
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2103
|
-
args: [
|
|
2104
|
-
eventId,
|
|
2105
|
-
id,
|
|
2106
|
-
valueObj._agentId || agentId,
|
|
2107
|
-
eventType,
|
|
2108
|
-
"memory:write_start",
|
|
2109
|
-
startTime,
|
|
2110
|
-
null,
|
|
2111
|
-
// no endTime
|
|
2112
|
-
"running",
|
|
2113
|
-
event.statusMessage || null,
|
|
2114
|
-
event.level || "INFO",
|
|
2115
|
-
event.version || null,
|
|
2116
|
-
event.parentEventId || null,
|
|
2117
|
-
null,
|
|
2118
|
-
// tags
|
|
2119
|
-
inputData,
|
|
2120
|
-
null,
|
|
2121
|
-
// no output
|
|
2122
|
-
null,
|
|
2123
|
-
// no error
|
|
2124
|
-
(0, import_utils2.safeStringify)({
|
|
2125
|
-
id: "memory",
|
|
2126
|
-
agentId: event.affectedNodeId?.split("_").pop()
|
|
2127
|
-
})
|
|
2128
|
-
]
|
|
2129
|
-
});
|
|
2130
|
-
await this.client.execute({
|
|
2131
|
-
sql: `INSERT OR REPLACE INTO ${timelineEventsTableName}
|
|
2132
|
-
(id, history_id, agent_id, event_type, event_name, start_time, end_time,
|
|
2133
|
-
status, status_message, level, version, parent_event_id,
|
|
2134
|
-
tags, input, output, error, metadata)
|
|
2135
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2136
|
-
args: [
|
|
2137
|
-
this.generateId(),
|
|
2138
|
-
// New ID
|
|
2139
|
-
id,
|
|
2140
|
-
valueObj._agentId || agentId,
|
|
2141
|
-
eventType,
|
|
2142
|
-
"memory:write_success",
|
|
2143
|
-
endTime,
|
|
2144
|
-
// End time
|
|
2145
|
-
endTime,
|
|
2146
|
-
"completed",
|
|
2147
|
-
event.statusMessage || null,
|
|
2148
|
-
event.level || "INFO",
|
|
2149
|
-
event.version || null,
|
|
2150
|
-
eventId,
|
|
2151
|
-
// Parent event ID
|
|
2152
|
-
null,
|
|
2153
|
-
// tags
|
|
2154
|
-
inputData,
|
|
2155
|
-
event.data.output ? (0, import_utils2.safeStringify)(event.data.output) : null,
|
|
2156
|
-
event.error ? (0, import_utils2.safeStringify)(event.error) : null,
|
|
2157
|
-
(0, import_utils2.safeStringify)({
|
|
2158
|
-
id: "memory",
|
|
2159
|
-
agentId: event.affectedNodeId?.split("_").pop()
|
|
2160
|
-
})
|
|
2161
|
-
]
|
|
2162
|
-
});
|
|
2163
|
-
} else if (eventName === "memory:getMessages") {
|
|
2164
|
-
await this.client.execute({
|
|
2165
|
-
sql: `INSERT OR REPLACE INTO ${timelineEventsTableName}
|
|
2166
|
-
(id, history_id, agent_id, event_type, event_name, start_time, end_time,
|
|
2167
|
-
status, status_message, level, version, parent_event_id,
|
|
2168
|
-
tags, input, output, error, metadata)
|
|
2169
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2170
|
-
args: [
|
|
2171
|
-
eventId,
|
|
2172
|
-
id,
|
|
2173
|
-
valueObj._agentId || agentId,
|
|
2174
|
-
eventType,
|
|
2175
|
-
"memory:read_start",
|
|
2176
|
-
startTime,
|
|
2177
|
-
null,
|
|
2178
|
-
// no endTime
|
|
2179
|
-
"running",
|
|
2180
|
-
event.statusMessage || null,
|
|
2181
|
-
event.level || "INFO",
|
|
2182
|
-
event.version || null,
|
|
2183
|
-
event.parentEventId || null,
|
|
2184
|
-
null,
|
|
2185
|
-
// tags
|
|
2186
|
-
inputData,
|
|
2187
|
-
null,
|
|
2188
|
-
// no output
|
|
2189
|
-
null,
|
|
2190
|
-
// no error
|
|
2191
|
-
(0, import_utils2.safeStringify)({
|
|
2192
|
-
id: "memory",
|
|
2193
|
-
agentId: event.affectedNodeId?.split("_").pop()
|
|
2194
|
-
})
|
|
2195
|
-
]
|
|
2196
|
-
});
|
|
2197
|
-
await this.client.execute({
|
|
2198
|
-
sql: `INSERT OR REPLACE INTO ${timelineEventsTableName}
|
|
2199
|
-
(id, history_id, agent_id, event_type, event_name, start_time, end_time,
|
|
2200
|
-
status, status_message, level, version, parent_event_id,
|
|
2201
|
-
tags, input, output, error, metadata)
|
|
2202
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2203
|
-
args: [
|
|
2204
|
-
this.generateId(),
|
|
2205
|
-
// New ID
|
|
2206
|
-
id,
|
|
2207
|
-
valueObj._agentId || agentId,
|
|
2208
|
-
eventType,
|
|
2209
|
-
"memory:read_success",
|
|
2210
|
-
endTime,
|
|
2211
|
-
// End time
|
|
2212
|
-
endTime,
|
|
2213
|
-
status,
|
|
2214
|
-
event.statusMessage || null,
|
|
2215
|
-
event.level || "INFO",
|
|
2216
|
-
event.version || null,
|
|
2217
|
-
eventId,
|
|
2218
|
-
// Parent event ID
|
|
2219
|
-
null,
|
|
2220
|
-
// tags
|
|
2221
|
-
inputData,
|
|
2222
|
-
event.data.output ? (0, import_utils2.safeStringify)(event.data.output) : null,
|
|
2223
|
-
event.error ? (0, import_utils2.safeStringify)(event.error) : null,
|
|
2224
|
-
(0, import_utils2.safeStringify)({
|
|
2225
|
-
id: "memory",
|
|
2226
|
-
agentId: event.affectedNodeId?.split("_").pop()
|
|
2227
|
-
})
|
|
2228
|
-
]
|
|
2229
|
-
});
|
|
2230
|
-
} else {
|
|
2231
|
-
await this.client.execute({
|
|
2232
|
-
sql: `INSERT OR REPLACE INTO ${timelineEventsTableName}
|
|
2233
|
-
(id, history_id, agent_id, event_type, event_name, start_time, end_time,
|
|
2234
|
-
status, status_message, level, version, parent_event_id,
|
|
2235
|
-
tags, input, output, error, metadata)
|
|
2236
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2237
|
-
args: [
|
|
2238
|
-
eventId,
|
|
2239
|
-
id,
|
|
2240
|
-
valueObj._agentId || agentId,
|
|
2241
|
-
eventType,
|
|
2242
|
-
eventName,
|
|
2243
|
-
startTime,
|
|
2244
|
-
endTime,
|
|
2245
|
-
status,
|
|
2246
|
-
event.statusMessage || null,
|
|
2247
|
-
event.level || "INFO",
|
|
2248
|
-
event.version || null,
|
|
2249
|
-
event.parentEventId || null,
|
|
2250
|
-
null,
|
|
2251
|
-
// tags
|
|
2252
|
-
inputData,
|
|
2253
|
-
event.output ? (0, import_utils2.safeStringify)(event.output) : null,
|
|
2254
|
-
event.error ? (0, import_utils2.safeStringify)(event.error) : null,
|
|
2255
|
-
metadata
|
|
2256
|
-
]
|
|
2257
|
-
});
|
|
2258
|
-
}
|
|
2259
|
-
} else if (eventType === "tool") {
|
|
2260
|
-
if (eventName === "tool_working") {
|
|
2261
|
-
await this.client.execute({
|
|
2262
|
-
sql: `INSERT OR REPLACE INTO ${timelineEventsTableName}
|
|
2263
|
-
(id, history_id, agent_id, event_type, event_name, start_time, end_time,
|
|
2264
|
-
status, status_message, level, version, parent_event_id,
|
|
2265
|
-
tags, input, output, error, metadata)
|
|
2266
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2267
|
-
args: [
|
|
2268
|
-
eventId,
|
|
2269
|
-
id,
|
|
2270
|
-
valueObj._agentId || agentId,
|
|
2271
|
-
eventType,
|
|
2272
|
-
"tool:start",
|
|
2273
|
-
startTime,
|
|
2274
|
-
null,
|
|
2275
|
-
// no endTime
|
|
2276
|
-
"running",
|
|
2277
|
-
event.statusMessage || null,
|
|
2278
|
-
event.level || "INFO",
|
|
2279
|
-
event.version || null,
|
|
2280
|
-
event.parentEventId || null,
|
|
2281
|
-
null,
|
|
2282
|
-
// tags
|
|
2283
|
-
inputData,
|
|
2284
|
-
null,
|
|
2285
|
-
// no output
|
|
2286
|
-
null,
|
|
2287
|
-
// no error
|
|
2288
|
-
(0, import_utils2.safeStringify)({
|
|
2289
|
-
id: event.affectedNodeId?.split("_").pop(),
|
|
2290
|
-
agentId: event.data?.metadata?.sourceAgentId,
|
|
2291
|
-
displayName: event.data.metadata.toolName
|
|
2292
|
-
})
|
|
2293
|
-
]
|
|
2294
|
-
});
|
|
2295
|
-
await this.client.execute({
|
|
2296
|
-
sql: `INSERT OR REPLACE INTO ${timelineEventsTableName}
|
|
2297
|
-
(id, history_id, agent_id, event_type, event_name, start_time, end_time,
|
|
2298
|
-
status, status_message, level, version, parent_event_id,
|
|
2299
|
-
tags, input, output, error, metadata)
|
|
2300
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2301
|
-
args: [
|
|
2302
|
-
this.generateId(),
|
|
2303
|
-
// New ID
|
|
2304
|
-
id,
|
|
2305
|
-
valueObj._agentId || agentId,
|
|
2306
|
-
eventType,
|
|
2307
|
-
"tool:success",
|
|
2308
|
-
endTime,
|
|
2309
|
-
// End time
|
|
2310
|
-
endTime,
|
|
2311
|
-
"completed",
|
|
2312
|
-
event.statusMessage || null,
|
|
2313
|
-
event.level || "INFO",
|
|
2314
|
-
event.version || null,
|
|
2315
|
-
eventId,
|
|
2316
|
-
// Parent event ID
|
|
2317
|
-
null,
|
|
2318
|
-
// tags
|
|
2319
|
-
inputData,
|
|
2320
|
-
event.data.output ? (0, import_utils2.safeStringify)(event.data.output) : null,
|
|
2321
|
-
event.error ? (0, import_utils2.safeStringify)(event.error) : null,
|
|
2322
|
-
(0, import_utils2.safeStringify)({
|
|
2323
|
-
id: event.affectedNodeId?.split("_").pop(),
|
|
2324
|
-
agentId: event.data?.metadata?.sourceAgentId,
|
|
2325
|
-
displayName: event.data.metadata.toolName
|
|
2326
|
-
})
|
|
2327
|
-
]
|
|
2328
|
-
});
|
|
2329
|
-
}
|
|
2330
|
-
} else {
|
|
2331
|
-
await this.client.execute({
|
|
2332
|
-
sql: `INSERT OR REPLACE INTO ${timelineEventsTableName}
|
|
2333
|
-
(id, history_id, agent_id, event_type, event_name, start_time, end_time,
|
|
2334
|
-
status, status_message, level, version, parent_event_id,
|
|
2335
|
-
tags, input, output, error, metadata)
|
|
2336
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2337
|
-
args: [
|
|
2338
|
-
eventId,
|
|
2339
|
-
id,
|
|
2340
|
-
valueObj._agentId || agentId,
|
|
2341
|
-
eventType,
|
|
2342
|
-
eventName,
|
|
2343
|
-
startTime,
|
|
2344
|
-
endTime,
|
|
2345
|
-
status,
|
|
2346
|
-
event.statusMessage || null,
|
|
2347
|
-
event.level || "INFO",
|
|
2348
|
-
event.version || null,
|
|
2349
|
-
event.parentEventId || null,
|
|
2350
|
-
null,
|
|
2351
|
-
// tags
|
|
2352
|
-
inputData,
|
|
2353
|
-
event.output ? (0, import_utils2.safeStringify)(event.output) : null,
|
|
2354
|
-
event.error ? (0, import_utils2.safeStringify)(event.error) : null,
|
|
2355
|
-
(0, import_utils2.safeStringify)({
|
|
2356
|
-
id: eventType === "retriever" ? "retriever" : event.type,
|
|
2357
|
-
agentId: event.affectedNodeId?.split("_").pop()
|
|
2358
|
-
})
|
|
2359
|
-
]
|
|
2360
|
-
});
|
|
2361
|
-
}
|
|
2362
|
-
} catch (error) {
|
|
2363
|
-
this.debug("Error processing event:", error);
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
1860
|
+
if (this.dimensions === null) {
|
|
1861
|
+
this.dimensions = item.vector.length;
|
|
1862
|
+
} else if (item.vector.length !== this.dimensions) {
|
|
1863
|
+
throw new Error(
|
|
1864
|
+
`Vector dimension mismatch. Expected ${this.dimensions}, got ${item.vector.length}`
|
|
1865
|
+
);
|
|
2366
1866
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
1867
|
+
const serializedVector = this.serializeVector(item.vector);
|
|
1868
|
+
const metadataJson = item.metadata ? JSON.stringify(item.metadata) : null;
|
|
1869
|
+
const content = item.content ?? null;
|
|
1870
|
+
stmts.push({
|
|
1871
|
+
sql: `INSERT OR REPLACE INTO ${tableName} (id, vector, dimensions, metadata, content, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
|
|
1872
|
+
args: [item.id, serializedVector, item.vector.length, metadataJson, content]
|
|
1873
|
+
});
|
|
2369
1874
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
await this.client.execute(`
|
|
2374
|
-
CREATE INDEX IF NOT EXISTS idx_${oldTableName}_agent_id
|
|
2375
|
-
ON ${oldTableName}(agent_id)
|
|
2376
|
-
`);
|
|
2377
|
-
await this.client.execute("COMMIT;");
|
|
2378
|
-
this.debug(`Total ${migratedCount} records successfully migrated`);
|
|
2379
|
-
if (createBackup && deleteBackupAfterSuccess) {
|
|
2380
|
-
await this.client.execute(`DROP TABLE IF EXISTS ${oldTableBackup};`);
|
|
2381
|
-
this.debug("Unnecessary backup deleted");
|
|
2382
|
-
}
|
|
2383
|
-
await this.setMigrationFlag("agent_history_data_migration", migratedCount);
|
|
2384
|
-
return {
|
|
2385
|
-
success: true,
|
|
2386
|
-
migratedCount,
|
|
2387
|
-
backupCreated: createBackup && !deleteBackupAfterSuccess
|
|
2388
|
-
};
|
|
2389
|
-
} catch (error) {
|
|
2390
|
-
await this.client.execute("ROLLBACK;");
|
|
2391
|
-
this.debug("Error occurred while migrating agent history data:", error);
|
|
2392
|
-
return {
|
|
2393
|
-
success: false,
|
|
2394
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
2395
|
-
backupCreated: options.createBackup
|
|
2396
|
-
};
|
|
1875
|
+
await this.client.batch(stmts, "write");
|
|
1876
|
+
}, `storeBatch ${batch.length} vectors`);
|
|
1877
|
+
this.logger.debug(`Batch of ${batch.length} vectors stored`);
|
|
2397
1878
|
}
|
|
2398
1879
|
}
|
|
2399
1880
|
/**
|
|
2400
|
-
*
|
|
2401
|
-
*
|
|
2402
|
-
* ⚠️ **CRITICAL WARNING: DESTRUCTIVE OPERATION** ⚠️
|
|
2403
|
-
*
|
|
2404
|
-
* This method performs a DESTRUCTIVE schema migration that:
|
|
2405
|
-
* - DROPS and recreates existing tables
|
|
2406
|
-
* - Creates temporary tables during migration
|
|
2407
|
-
* - Modifies the primary key structure of the messages table
|
|
2408
|
-
* - Can cause DATA LOSS if interrupted or if errors occur
|
|
2409
|
-
*
|
|
2410
|
-
* **IMPORTANT SAFETY REQUIREMENTS:**
|
|
2411
|
-
* - 🛑 STOP all application instances before running this migration
|
|
2412
|
-
* - 🛑 Ensure NO concurrent database operations are running
|
|
2413
|
-
* - 🛑 Take a full database backup before running (independent of built-in backup)
|
|
2414
|
-
* - 🛑 Test the migration on a copy of production data first
|
|
2415
|
-
* - 🛑 Plan for downtime during migration execution
|
|
2416
|
-
*
|
|
2417
|
-
* **What this migration does:**
|
|
2418
|
-
* 1. Creates backup tables (if createBackup=true)
|
|
2419
|
-
* 2. Creates temporary tables with new schema
|
|
2420
|
-
* 3. Migrates data from old tables to new schema
|
|
2421
|
-
* 4. DROPS original tables
|
|
2422
|
-
* 5. Renames temporary tables to original names
|
|
2423
|
-
* 6. All operations are wrapped in a transaction for atomicity
|
|
2424
|
-
*
|
|
2425
|
-
* @param options Migration configuration options
|
|
2426
|
-
* @param options.createBackup Whether to create backup tables before migration (default: true, HIGHLY RECOMMENDED)
|
|
2427
|
-
* @param options.restoreFromBackup Whether to restore from existing backup instead of migrating (default: false)
|
|
2428
|
-
* @param options.deleteBackupAfterSuccess Whether to delete backup tables after successful migration (default: false)
|
|
2429
|
-
*
|
|
2430
|
-
* @returns Promise resolving to migration result with success status, migrated count, and backup info
|
|
2431
|
-
*
|
|
2432
|
-
* @example
|
|
2433
|
-
* ```typescript
|
|
2434
|
-
* // RECOMMENDED: Run with backup creation (default)
|
|
2435
|
-
* const result = await storage.migrateConversationSchema({
|
|
2436
|
-
* createBackup: true,
|
|
2437
|
-
* deleteBackupAfterSuccess: false // Keep backup for safety
|
|
2438
|
-
* });
|
|
2439
|
-
*
|
|
2440
|
-
* if (result.success) {
|
|
2441
|
-
* console.log(`Migrated ${result.migratedCount} conversations successfully`);
|
|
2442
|
-
* } else {
|
|
2443
|
-
* console.error('Migration failed:', result.error);
|
|
2444
|
-
* // Consider restoring from backup
|
|
2445
|
-
* }
|
|
2446
|
-
*
|
|
2447
|
-
* // If migration fails, restore from backup:
|
|
2448
|
-
* const restoreResult = await storage.migrateConversationSchema({
|
|
2449
|
-
* restoreFromBackup: true
|
|
2450
|
-
* });
|
|
2451
|
-
* ```
|
|
2452
|
-
*
|
|
2453
|
-
* @throws {Error} If migration fails and transaction is rolled back
|
|
2454
|
-
*
|
|
2455
|
-
* @since This migration is typically only needed when upgrading from older schema versions
|
|
1881
|
+
* Search for similar vectors using cosine similarity
|
|
2456
1882
|
*/
|
|
2457
|
-
async
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
const conversationsTableName = `${this.options.tablePrefix}_conversations`;
|
|
2464
|
-
const messagesTableName = `${this.options.tablePrefix}_messages`;
|
|
2465
|
-
const conversationsBackupName = `${conversationsTableName}_backup`;
|
|
2466
|
-
const messagesBackupName = `${messagesTableName}_backup`;
|
|
2467
|
-
try {
|
|
2468
|
-
this.debug("Starting conversation schema migration...");
|
|
2469
|
-
const flagCheck = await this.checkMigrationFlag("conversation_schema_migration");
|
|
2470
|
-
if (flagCheck.alreadyCompleted) {
|
|
2471
|
-
return { success: true, migratedCount: 0 };
|
|
2472
|
-
}
|
|
2473
|
-
if (restoreFromBackup) {
|
|
2474
|
-
this.debug("Starting restoration from backup...");
|
|
2475
|
-
const convBackupCheck = await this.client.execute({
|
|
2476
|
-
sql: "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
2477
|
-
args: [conversationsBackupName]
|
|
2478
|
-
});
|
|
2479
|
-
const msgBackupCheck = await this.client.execute({
|
|
2480
|
-
sql: "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
2481
|
-
args: [messagesBackupName]
|
|
2482
|
-
});
|
|
2483
|
-
if (convBackupCheck.rows.length === 0 || msgBackupCheck.rows.length === 0) {
|
|
2484
|
-
throw new Error("No backup found to restore");
|
|
2485
|
-
}
|
|
2486
|
-
await this.client.execute("BEGIN TRANSACTION;");
|
|
2487
|
-
await this.client.execute(`DROP TABLE IF EXISTS ${conversationsTableName};`);
|
|
2488
|
-
await this.client.execute(`DROP TABLE IF EXISTS ${messagesTableName};`);
|
|
2489
|
-
await this.client.execute(
|
|
2490
|
-
`ALTER TABLE ${conversationsBackupName} RENAME TO ${conversationsTableName};`
|
|
2491
|
-
);
|
|
2492
|
-
await this.client.execute(
|
|
2493
|
-
`ALTER TABLE ${messagesBackupName} RENAME TO ${messagesTableName};`
|
|
2494
|
-
);
|
|
2495
|
-
await this.client.execute("COMMIT;");
|
|
2496
|
-
this.debug("Restoration from backup completed successfully");
|
|
2497
|
-
return { success: true, backupCreated: false };
|
|
2498
|
-
}
|
|
2499
|
-
const convTableInfo = await this.client.execute(
|
|
2500
|
-
`PRAGMA table_info(${conversationsTableName})`
|
|
1883
|
+
async search(queryVector, options) {
|
|
1884
|
+
await this.initialize();
|
|
1885
|
+
const { limit = 10, threshold = 0, filter } = options || {};
|
|
1886
|
+
if (this.dimensions !== null && queryVector.length !== this.dimensions) {
|
|
1887
|
+
throw new Error(
|
|
1888
|
+
`Query vector dimension mismatch. Expected ${this.dimensions}, got ${queryVector.length}`
|
|
2501
1889
|
);
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
await this.client.execute(
|
|
2524
|
-
`CREATE TABLE ${messagesBackupName} AS SELECT * FROM ${messagesTableName};`
|
|
2525
|
-
);
|
|
2526
|
-
}
|
|
2527
|
-
this.debug("Backups created successfully");
|
|
2528
|
-
}
|
|
2529
|
-
let conversationData = [];
|
|
2530
|
-
let messageData = [];
|
|
2531
|
-
if (convTableInfo.rows.length > 0) {
|
|
2532
|
-
const convResult = await this.client.execute(`SELECT * FROM ${conversationsTableName}`);
|
|
2533
|
-
conversationData = convResult.rows;
|
|
2534
|
-
}
|
|
2535
|
-
if (msgTableInfo.rows.length > 0) {
|
|
2536
|
-
const msgResult = await this.client.execute(`SELECT * FROM ${messagesTableName}`);
|
|
2537
|
-
messageData = msgResult.rows;
|
|
1890
|
+
}
|
|
1891
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
1892
|
+
let query = `SELECT id, vector, dimensions, metadata, content FROM ${tableName}`;
|
|
1893
|
+
const args = [];
|
|
1894
|
+
if (this.dimensions !== null) {
|
|
1895
|
+
query += " WHERE dimensions = ?";
|
|
1896
|
+
args.push(this.dimensions);
|
|
1897
|
+
}
|
|
1898
|
+
const result = await this.executeWithRetry(
|
|
1899
|
+
async () => await this.client.execute({ sql: query, args }),
|
|
1900
|
+
"search vectors"
|
|
1901
|
+
);
|
|
1902
|
+
const searchResults = [];
|
|
1903
|
+
for (const row of result.rows) {
|
|
1904
|
+
const id = row.id;
|
|
1905
|
+
const vectorBlob = row.vector;
|
|
1906
|
+
const metadataJson = row.metadata;
|
|
1907
|
+
const content = row.content ?? void 0;
|
|
1908
|
+
const metadata = metadataJson ? JSON.parse(metadataJson) : void 0;
|
|
1909
|
+
if (filter && !this.matchesFilter(metadata, filter)) {
|
|
1910
|
+
continue;
|
|
2538
1911
|
}
|
|
2539
|
-
|
|
2540
|
-
const
|
|
2541
|
-
const
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
id
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
)
|
|
2552
|
-
`);
|
|
2553
|
-
await this.client.execute(`
|
|
2554
|
-
CREATE TABLE ${tempMessagesTable} (
|
|
2555
|
-
conversation_id TEXT NOT NULL,
|
|
2556
|
-
message_id TEXT NOT NULL,
|
|
2557
|
-
role TEXT NOT NULL,
|
|
2558
|
-
content TEXT NOT NULL,
|
|
2559
|
-
type TEXT NOT NULL,
|
|
2560
|
-
created_at TEXT NOT NULL,
|
|
2561
|
-
PRIMARY KEY (conversation_id, message_id)
|
|
2562
|
-
)
|
|
2563
|
-
`);
|
|
2564
|
-
let migratedCount = 0;
|
|
2565
|
-
const createdConversations = /* @__PURE__ */ new Set();
|
|
2566
|
-
for (const row of messageData) {
|
|
2567
|
-
const conversationId = row.conversation_id;
|
|
2568
|
-
let userId = "default";
|
|
2569
|
-
if (hasUserIdInMessages && row.user_id) {
|
|
2570
|
-
userId = row.user_id;
|
|
2571
|
-
}
|
|
2572
|
-
if (!createdConversations.has(conversationId)) {
|
|
2573
|
-
const existingConversation = conversationData.find((conv) => conv.id === conversationId);
|
|
2574
|
-
if (existingConversation) {
|
|
2575
|
-
let convUserId = userId;
|
|
2576
|
-
if (hasUserIdInConversations && existingConversation.user_id) {
|
|
2577
|
-
convUserId = existingConversation.user_id;
|
|
2578
|
-
}
|
|
2579
|
-
await this.client.execute({
|
|
2580
|
-
sql: `INSERT INTO ${tempConversationsTable}
|
|
2581
|
-
(id, resource_id, user_id, title, metadata, created_at, updated_at)
|
|
2582
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2583
|
-
args: [
|
|
2584
|
-
existingConversation.id,
|
|
2585
|
-
existingConversation.resource_id,
|
|
2586
|
-
convUserId,
|
|
2587
|
-
existingConversation.title,
|
|
2588
|
-
existingConversation.metadata,
|
|
2589
|
-
existingConversation.created_at,
|
|
2590
|
-
existingConversation.updated_at
|
|
2591
|
-
]
|
|
2592
|
-
});
|
|
2593
|
-
} else {
|
|
2594
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2595
|
-
await this.client.execute({
|
|
2596
|
-
sql: `INSERT INTO ${tempConversationsTable}
|
|
2597
|
-
(id, resource_id, user_id, title, metadata, created_at, updated_at)
|
|
2598
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
2599
|
-
args: [
|
|
2600
|
-
conversationId,
|
|
2601
|
-
"default",
|
|
2602
|
-
// Default resource_id for auto-created conversations
|
|
2603
|
-
userId,
|
|
2604
|
-
"Migrated Conversation",
|
|
2605
|
-
// Default title
|
|
2606
|
-
(0, import_utils2.safeStringify)({}),
|
|
2607
|
-
// Empty metadata
|
|
2608
|
-
now,
|
|
2609
|
-
now
|
|
2610
|
-
]
|
|
2611
|
-
});
|
|
2612
|
-
}
|
|
2613
|
-
createdConversations.add(conversationId);
|
|
2614
|
-
migratedCount++;
|
|
2615
|
-
}
|
|
2616
|
-
await this.client.execute({
|
|
2617
|
-
sql: `INSERT INTO ${tempMessagesTable}
|
|
2618
|
-
(conversation_id, message_id, role, content, type, created_at)
|
|
2619
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
2620
|
-
args: [
|
|
2621
|
-
row.conversation_id,
|
|
2622
|
-
row.message_id,
|
|
2623
|
-
row.role,
|
|
2624
|
-
row.content,
|
|
2625
|
-
row.type,
|
|
2626
|
-
row.created_at
|
|
2627
|
-
]
|
|
1912
|
+
const vector = this.deserializeVector(vectorBlob);
|
|
1913
|
+
const similarity = (0, import_core2.cosineSimilarity)(queryVector, vector);
|
|
1914
|
+
const score = (similarity + 1) / 2;
|
|
1915
|
+
if (score >= threshold) {
|
|
1916
|
+
searchResults.push({
|
|
1917
|
+
id,
|
|
1918
|
+
vector,
|
|
1919
|
+
metadata,
|
|
1920
|
+
content,
|
|
1921
|
+
score,
|
|
1922
|
+
distance: 1 - similarity
|
|
1923
|
+
// Convert to distance metric
|
|
2628
1924
|
});
|
|
2629
1925
|
}
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
userId,
|
|
2645
|
-
row.title,
|
|
2646
|
-
row.metadata,
|
|
2647
|
-
row.created_at,
|
|
2648
|
-
row.updated_at
|
|
2649
|
-
]
|
|
2650
|
-
});
|
|
2651
|
-
migratedCount++;
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
await this.client.execute(`DROP TABLE IF EXISTS ${conversationsTableName};`);
|
|
2655
|
-
await this.client.execute(`DROP TABLE IF EXISTS ${messagesTableName};`);
|
|
2656
|
-
await this.client.execute(
|
|
2657
|
-
`ALTER TABLE ${tempConversationsTable} RENAME TO ${conversationsTableName};`
|
|
2658
|
-
);
|
|
2659
|
-
await this.client.execute(`ALTER TABLE ${tempMessagesTable} RENAME TO ${messagesTableName};`);
|
|
2660
|
-
await this.client.execute(`
|
|
2661
|
-
CREATE INDEX IF NOT EXISTS idx_${messagesTableName}_lookup
|
|
2662
|
-
ON ${messagesTableName}(conversation_id, created_at)
|
|
2663
|
-
`);
|
|
2664
|
-
await this.client.execute(`
|
|
2665
|
-
CREATE INDEX IF NOT EXISTS idx_${conversationsTableName}_resource
|
|
2666
|
-
ON ${conversationsTableName}(resource_id)
|
|
2667
|
-
`);
|
|
2668
|
-
await this.client.execute(`
|
|
2669
|
-
CREATE INDEX IF NOT EXISTS idx_${conversationsTableName}_user
|
|
2670
|
-
ON ${conversationsTableName}(user_id)
|
|
2671
|
-
`);
|
|
2672
|
-
await this.client.execute("COMMIT;");
|
|
2673
|
-
if (deleteBackupAfterSuccess) {
|
|
2674
|
-
await this.client.execute(`DROP TABLE IF EXISTS ${conversationsBackupName};`);
|
|
2675
|
-
await this.client.execute(`DROP TABLE IF EXISTS ${messagesBackupName};`);
|
|
2676
|
-
}
|
|
2677
|
-
await this.setMigrationFlag("conversation_schema_migration", migratedCount);
|
|
2678
|
-
this.debug(
|
|
2679
|
-
`Conversation schema migration completed successfully. Migrated ${migratedCount} conversations.`
|
|
2680
|
-
);
|
|
2681
|
-
return {
|
|
2682
|
-
success: true,
|
|
2683
|
-
migratedCount,
|
|
2684
|
-
backupCreated: createBackup
|
|
2685
|
-
};
|
|
2686
|
-
} catch (error) {
|
|
2687
|
-
this.debug("Error during conversation schema migration:", error);
|
|
2688
|
-
try {
|
|
2689
|
-
await this.client.execute("ROLLBACK;");
|
|
2690
|
-
} catch (rollbackError) {
|
|
2691
|
-
this.debug("Error rolling back transaction:", rollbackError);
|
|
1926
|
+
}
|
|
1927
|
+
searchResults.sort((a, b) => b.score - a.score);
|
|
1928
|
+
return searchResults.slice(0, limit);
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Check if metadata matches the filter criteria
|
|
1932
|
+
*/
|
|
1933
|
+
matchesFilter(metadata, filter) {
|
|
1934
|
+
if (!metadata) {
|
|
1935
|
+
return false;
|
|
1936
|
+
}
|
|
1937
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
1938
|
+
if (metadata[key] !== value) {
|
|
1939
|
+
return false;
|
|
2692
1940
|
}
|
|
2693
|
-
return {
|
|
2694
|
-
success: false,
|
|
2695
|
-
error,
|
|
2696
|
-
backupCreated: createBackup
|
|
2697
|
-
};
|
|
2698
1941
|
}
|
|
1942
|
+
return true;
|
|
2699
1943
|
}
|
|
2700
1944
|
/**
|
|
2701
|
-
*
|
|
2702
|
-
* @param userId User ID to filter by
|
|
2703
|
-
* @returns Query builder object
|
|
1945
|
+
* Delete a vector by ID
|
|
2704
1946
|
*/
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
* @param direction Sort direction
|
|
2717
|
-
* @returns Query builder
|
|
2718
|
-
*/
|
|
2719
|
-
orderBy: /* @__PURE__ */ __name((field = "updated_at", direction = "DESC") => ({
|
|
2720
|
-
/**
|
|
2721
|
-
* Execute the query and return results
|
|
2722
|
-
* @returns Promise of conversations
|
|
2723
|
-
*/
|
|
2724
|
-
execute: /* @__PURE__ */ __name(() => this.getConversationsByUserId(userId, {
|
|
2725
|
-
limit: count,
|
|
2726
|
-
orderBy: field,
|
|
2727
|
-
orderDirection: direction
|
|
2728
|
-
}), "execute")
|
|
2729
|
-
}), "orderBy"),
|
|
2730
|
-
/**
|
|
2731
|
-
* Execute the query with default ordering
|
|
2732
|
-
* @returns Promise of conversations
|
|
2733
|
-
*/
|
|
2734
|
-
execute: /* @__PURE__ */ __name(() => this.getConversationsByUserId(userId, { limit: count }), "execute")
|
|
2735
|
-
}), "limit"),
|
|
2736
|
-
/**
|
|
2737
|
-
* Order results by a specific field
|
|
2738
|
-
* @param field Field to order by
|
|
2739
|
-
* @param direction Sort direction
|
|
2740
|
-
* @returns Query builder
|
|
2741
|
-
*/
|
|
2742
|
-
orderBy: /* @__PURE__ */ __name((field = "updated_at", direction = "DESC") => ({
|
|
2743
|
-
/**
|
|
2744
|
-
* Limit the number of results
|
|
2745
|
-
* @param count Number of conversations to return
|
|
2746
|
-
* @returns Query builder
|
|
2747
|
-
*/
|
|
2748
|
-
limit: /* @__PURE__ */ __name((count) => ({
|
|
2749
|
-
/**
|
|
2750
|
-
* Execute the query and return results
|
|
2751
|
-
* @returns Promise of conversations
|
|
2752
|
-
*/
|
|
2753
|
-
execute: /* @__PURE__ */ __name(() => this.getConversationsByUserId(userId, {
|
|
2754
|
-
limit: count,
|
|
2755
|
-
orderBy: field,
|
|
2756
|
-
orderDirection: direction
|
|
2757
|
-
}), "execute")
|
|
2758
|
-
}), "limit"),
|
|
2759
|
-
/**
|
|
2760
|
-
* Execute the query without limit
|
|
2761
|
-
* @returns Promise of conversations
|
|
2762
|
-
*/
|
|
2763
|
-
execute: /* @__PURE__ */ __name(() => this.getConversationsByUserId(userId, {
|
|
2764
|
-
orderBy: field,
|
|
2765
|
-
orderDirection: direction
|
|
2766
|
-
}), "execute")
|
|
2767
|
-
}), "orderBy"),
|
|
2768
|
-
/**
|
|
2769
|
-
* Execute the query with default options
|
|
2770
|
-
* @returns Promise of conversations
|
|
2771
|
-
*/
|
|
2772
|
-
execute: /* @__PURE__ */ __name(() => this.getConversationsByUserId(userId), "execute")
|
|
2773
|
-
};
|
|
1947
|
+
async delete(id) {
|
|
1948
|
+
await this.initialize();
|
|
1949
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
1950
|
+
await this.executeWithRetry(async () => {
|
|
1951
|
+
await this.client.execute({
|
|
1952
|
+
sql: `DELETE FROM ${tableName} WHERE id = ?`,
|
|
1953
|
+
args: [id]
|
|
1954
|
+
});
|
|
1955
|
+
}, `delete vector ${id}`);
|
|
1956
|
+
this.vectorCache.delete(id);
|
|
1957
|
+
this.logger.debug(`Vector deleted: ${id}`);
|
|
2774
1958
|
}
|
|
2775
1959
|
/**
|
|
2776
|
-
*
|
|
2777
|
-
* @param conversationId Conversation ID
|
|
2778
|
-
* @param userId User ID to validate ownership
|
|
2779
|
-
* @returns Conversation or null
|
|
1960
|
+
* Delete multiple vectors by IDs
|
|
2780
1961
|
*/
|
|
2781
|
-
async
|
|
2782
|
-
|
|
2783
|
-
if (
|
|
2784
|
-
|
|
1962
|
+
async deleteBatch(ids) {
|
|
1963
|
+
await this.initialize();
|
|
1964
|
+
if (ids.length === 0) return;
|
|
1965
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
1966
|
+
for (let i = 0; i < ids.length; i += this.batchSize) {
|
|
1967
|
+
const batch = ids.slice(i, i + this.batchSize);
|
|
1968
|
+
const placeholders = batch.map(() => "?").join(",");
|
|
1969
|
+
await this.executeWithRetry(async () => {
|
|
1970
|
+
await this.client.execute({
|
|
1971
|
+
sql: `DELETE FROM ${tableName} WHERE id IN (${placeholders})`,
|
|
1972
|
+
args: batch
|
|
1973
|
+
});
|
|
1974
|
+
}, `deleteBatch ${batch.length} vectors`);
|
|
1975
|
+
for (const id of batch) {
|
|
1976
|
+
this.vectorCache.delete(id);
|
|
1977
|
+
}
|
|
1978
|
+
this.logger.debug(`Batch of ${batch.length} vectors deleted`);
|
|
2785
1979
|
}
|
|
2786
|
-
return conversation;
|
|
2787
1980
|
}
|
|
2788
1981
|
/**
|
|
2789
|
-
*
|
|
2790
|
-
* @param userId User ID
|
|
2791
|
-
* @param page Page number (1-based)
|
|
2792
|
-
* @param pageSize Number of items per page
|
|
2793
|
-
* @returns Object with conversations and pagination info
|
|
1982
|
+
* Clear all vectors
|
|
2794
1983
|
*/
|
|
2795
|
-
async
|
|
2796
|
-
|
|
2797
|
-
const
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
const results = hasMore ? conversations.slice(0, pageSize) : conversations;
|
|
2805
|
-
return {
|
|
2806
|
-
conversations: results,
|
|
2807
|
-
page,
|
|
2808
|
-
pageSize,
|
|
2809
|
-
hasMore
|
|
2810
|
-
};
|
|
1984
|
+
async clear() {
|
|
1985
|
+
await this.initialize();
|
|
1986
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
1987
|
+
await this.executeWithRetry(async () => {
|
|
1988
|
+
await this.client.execute(`DELETE FROM ${tableName}`);
|
|
1989
|
+
}, "clear all vectors");
|
|
1990
|
+
this.vectorCache.clear();
|
|
1991
|
+
this.dimensions = null;
|
|
1992
|
+
this.logger.debug("All vectors cleared");
|
|
2811
1993
|
}
|
|
2812
1994
|
/**
|
|
2813
|
-
*
|
|
2814
|
-
* @param migrationType Type of migration to check
|
|
2815
|
-
* @returns Object with completion status and details
|
|
1995
|
+
* Get total count of stored vectors
|
|
2816
1996
|
*/
|
|
2817
|
-
async
|
|
2818
|
-
|
|
2819
|
-
const
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
1997
|
+
async count() {
|
|
1998
|
+
await this.initialize();
|
|
1999
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
2000
|
+
const result = await this.executeWithRetry(
|
|
2001
|
+
async () => await this.client.execute(`SELECT COUNT(*) as count FROM ${tableName}`),
|
|
2002
|
+
"count vectors"
|
|
2003
|
+
);
|
|
2004
|
+
const raw = result.rows[0]?.count;
|
|
2005
|
+
if (typeof raw === "bigint") return Number(raw);
|
|
2006
|
+
if (typeof raw === "string") return Number.parseInt(raw, 10) || 0;
|
|
2007
|
+
return raw ?? 0;
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Get a specific vector by ID
|
|
2011
|
+
*/
|
|
2012
|
+
async get(id) {
|
|
2013
|
+
await this.initialize();
|
|
2014
|
+
if (this.vectorCache.has(id)) {
|
|
2015
|
+
const cached = this.vectorCache.get(id);
|
|
2016
|
+
if (cached) {
|
|
2830
2017
|
return {
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2018
|
+
...cached,
|
|
2019
|
+
vector: [...cached.vector],
|
|
2020
|
+
// Return a copy
|
|
2021
|
+
metadata: cached.metadata ? { ...cached.metadata } : void 0
|
|
2834
2022
|
};
|
|
2835
2023
|
}
|
|
2836
|
-
this.debug("Migration flags table found, but no migration flag exists yet");
|
|
2837
|
-
return { alreadyCompleted: false };
|
|
2838
|
-
} catch (flagError) {
|
|
2839
|
-
this.debug("Migration flag table not found, creating it...");
|
|
2840
|
-
this.debug("Original error:", flagError);
|
|
2841
|
-
try {
|
|
2842
|
-
await this.client.execute(`
|
|
2843
|
-
CREATE TABLE IF NOT EXISTS ${migrationFlagTable} (
|
|
2844
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2845
|
-
migration_type TEXT NOT NULL UNIQUE,
|
|
2846
|
-
completed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
2847
|
-
migrated_count INTEGER DEFAULT 0,
|
|
2848
|
-
metadata TEXT DEFAULT '{}'
|
|
2849
|
-
)
|
|
2850
|
-
`);
|
|
2851
|
-
this.debug("Migration flags table created successfully");
|
|
2852
|
-
} catch (createError) {
|
|
2853
|
-
this.debug("Failed to create migration flags table:", createError);
|
|
2854
|
-
}
|
|
2855
|
-
return { alreadyCompleted: false };
|
|
2856
2024
|
}
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2025
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
2026
|
+
const result = await this.executeWithRetry(
|
|
2027
|
+
async () => await this.client.execute({
|
|
2028
|
+
sql: `SELECT id, vector, metadata, content FROM ${tableName} WHERE id = ?`,
|
|
2029
|
+
args: [id]
|
|
2030
|
+
}),
|
|
2031
|
+
`get vector ${id}`
|
|
2032
|
+
);
|
|
2033
|
+
if (result.rows.length === 0) {
|
|
2034
|
+
return null;
|
|
2035
|
+
}
|
|
2036
|
+
const row = result.rows[0];
|
|
2037
|
+
const vectorBlob = row.vector;
|
|
2038
|
+
const metadataJson = row.metadata;
|
|
2039
|
+
const content = row.content;
|
|
2040
|
+
const vector = this.deserializeVector(vectorBlob);
|
|
2041
|
+
const metadata = metadataJson ? JSON.parse(metadataJson) : void 0;
|
|
2042
|
+
const item = {
|
|
2043
|
+
id,
|
|
2044
|
+
vector,
|
|
2045
|
+
metadata,
|
|
2046
|
+
content: content ?? void 0
|
|
2047
|
+
};
|
|
2048
|
+
if (this.vectorCache.size >= this.cacheSize) {
|
|
2049
|
+
const firstKey = this.vectorCache.keys().next().value;
|
|
2050
|
+
if (firstKey) this.vectorCache.delete(firstKey);
|
|
2876
2051
|
}
|
|
2052
|
+
this.vectorCache.set(id, item);
|
|
2053
|
+
return item;
|
|
2877
2054
|
}
|
|
2878
2055
|
/**
|
|
2879
|
-
*
|
|
2056
|
+
* Close the database connection
|
|
2880
2057
|
*/
|
|
2881
|
-
async
|
|
2882
|
-
|
|
2058
|
+
async close() {
|
|
2059
|
+
this.vectorCache.clear();
|
|
2060
|
+
this.logger.debug("Vector adapter closed");
|
|
2883
2061
|
try {
|
|
2884
|
-
this.
|
|
2885
|
-
|
|
2886
|
-
if (flagCheck.alreadyCompleted) {
|
|
2887
|
-
return { success: true };
|
|
2888
|
-
}
|
|
2889
|
-
const tableInfo = await this.client.execute(`PRAGMA table_info(${historyTableName})`);
|
|
2890
|
-
if (tableInfo.rows.length === 0) {
|
|
2891
|
-
this.debug("Agent history table doesn't exist, migration not needed");
|
|
2892
|
-
return { success: true };
|
|
2893
|
-
}
|
|
2894
|
-
const hasUserIdColumn = tableInfo.rows.some((row) => row.name === "userId");
|
|
2895
|
-
const hasConversationIdColumn = tableInfo.rows.some((row) => row.name === "conversationId");
|
|
2896
|
-
if (hasUserIdColumn && hasConversationIdColumn) {
|
|
2897
|
-
this.debug("Both userId and conversationId columns already exist, skipping migration");
|
|
2898
|
-
await this.setMigrationFlag("agent_history_schema_migration", 0);
|
|
2899
|
-
return { success: true };
|
|
2900
|
-
}
|
|
2901
|
-
if (!hasUserIdColumn) {
|
|
2902
|
-
await this.client.execute(`ALTER TABLE ${historyTableName} ADD COLUMN userId TEXT`);
|
|
2903
|
-
this.debug("Added userId column to agent history table");
|
|
2904
|
-
}
|
|
2905
|
-
if (!hasConversationIdColumn) {
|
|
2906
|
-
await this.client.execute(`ALTER TABLE ${historyTableName} ADD COLUMN conversationId TEXT`);
|
|
2907
|
-
this.debug("Added conversationId column to agent history table");
|
|
2908
|
-
}
|
|
2909
|
-
if (!hasUserIdColumn) {
|
|
2910
|
-
await this.client.execute(`
|
|
2911
|
-
CREATE INDEX IF NOT EXISTS idx_${historyTableName}_userId
|
|
2912
|
-
ON ${historyTableName}(userId)
|
|
2913
|
-
`);
|
|
2914
|
-
}
|
|
2915
|
-
if (!hasConversationIdColumn) {
|
|
2916
|
-
await this.client.execute(`
|
|
2917
|
-
CREATE INDEX IF NOT EXISTS idx_${historyTableName}_conversationId
|
|
2918
|
-
ON ${historyTableName}(conversationId)
|
|
2919
|
-
`);
|
|
2920
|
-
}
|
|
2921
|
-
await this.setMigrationFlag("agent_history_schema_migration", 0);
|
|
2922
|
-
this.debug("Agent history schema migration completed successfully");
|
|
2923
|
-
return { success: true };
|
|
2924
|
-
} catch (error) {
|
|
2925
|
-
this.debug("Error during agent history schema migration:", error);
|
|
2926
|
-
return {
|
|
2927
|
-
success: false,
|
|
2928
|
-
error
|
|
2929
|
-
};
|
|
2062
|
+
this.client?.close?.();
|
|
2063
|
+
} catch {
|
|
2930
2064
|
}
|
|
2931
2065
|
}
|
|
2932
|
-
// ===== WorkflowMemory Interface Implementation =====
|
|
2933
|
-
// Delegate all workflow operations to the workflow extension
|
|
2934
|
-
async storeWorkflowHistory(entry) {
|
|
2935
|
-
await this.initialized;
|
|
2936
|
-
return this.workflowExtension.storeWorkflowHistory(entry);
|
|
2937
|
-
}
|
|
2938
|
-
async getWorkflowHistory(id) {
|
|
2939
|
-
await this.initialized;
|
|
2940
|
-
return this.workflowExtension.getWorkflowHistory(id);
|
|
2941
|
-
}
|
|
2942
|
-
async getWorkflowHistoryByWorkflowId(workflowId) {
|
|
2943
|
-
await this.initialized;
|
|
2944
|
-
return this.workflowExtension.getWorkflowHistoryByWorkflowId(workflowId);
|
|
2945
|
-
}
|
|
2946
|
-
async updateWorkflowHistory(id, updates) {
|
|
2947
|
-
await this.initialized;
|
|
2948
|
-
return this.workflowExtension.updateWorkflowHistory(id, updates);
|
|
2949
|
-
}
|
|
2950
|
-
async deleteWorkflowHistory(id) {
|
|
2951
|
-
await this.initialized;
|
|
2952
|
-
return this.workflowExtension.deleteWorkflowHistory(id);
|
|
2953
|
-
}
|
|
2954
|
-
async storeWorkflowStep(step) {
|
|
2955
|
-
await this.initialized;
|
|
2956
|
-
return this.workflowExtension.storeWorkflowStep(step);
|
|
2957
|
-
}
|
|
2958
|
-
async getWorkflowStep(id) {
|
|
2959
|
-
await this.initialized;
|
|
2960
|
-
return this.workflowExtension.getWorkflowStep(id);
|
|
2961
|
-
}
|
|
2962
|
-
async getWorkflowSteps(workflowHistoryId) {
|
|
2963
|
-
await this.initialized;
|
|
2964
|
-
return this.workflowExtension.getWorkflowSteps(workflowHistoryId);
|
|
2965
|
-
}
|
|
2966
|
-
async updateWorkflowStep(id, updates) {
|
|
2967
|
-
await this.initialized;
|
|
2968
|
-
return this.workflowExtension.updateWorkflowStep(id, updates);
|
|
2969
|
-
}
|
|
2970
|
-
async deleteWorkflowStep(id) {
|
|
2971
|
-
await this.initialized;
|
|
2972
|
-
return this.workflowExtension.deleteWorkflowStep(id);
|
|
2973
|
-
}
|
|
2974
|
-
async storeWorkflowTimelineEvent(event) {
|
|
2975
|
-
await this.initialized;
|
|
2976
|
-
return this.workflowExtension.storeWorkflowTimelineEvent(event);
|
|
2977
|
-
}
|
|
2978
|
-
async getWorkflowTimelineEvent(id) {
|
|
2979
|
-
await this.initialized;
|
|
2980
|
-
return this.workflowExtension.getWorkflowTimelineEvent(id);
|
|
2981
|
-
}
|
|
2982
|
-
async getWorkflowTimelineEvents(workflowHistoryId) {
|
|
2983
|
-
await this.initialized;
|
|
2984
|
-
return this.workflowExtension.getWorkflowTimelineEvents(workflowHistoryId);
|
|
2985
|
-
}
|
|
2986
|
-
async deleteWorkflowTimelineEvent(id) {
|
|
2987
|
-
await this.initialized;
|
|
2988
|
-
return this.workflowExtension.deleteWorkflowTimelineEvent(id);
|
|
2989
|
-
}
|
|
2990
|
-
async getAllWorkflowIds() {
|
|
2991
|
-
await this.initialized;
|
|
2992
|
-
return this.workflowExtension.getAllWorkflowIds();
|
|
2993
|
-
}
|
|
2994
|
-
async getWorkflowStats(workflowId) {
|
|
2995
|
-
await this.initialized;
|
|
2996
|
-
return this.workflowExtension.getWorkflowStats(workflowId);
|
|
2997
|
-
}
|
|
2998
|
-
async getWorkflowHistoryWithStepsAndEvents(id) {
|
|
2999
|
-
await this.initialized;
|
|
3000
|
-
return this.workflowExtension.getWorkflowHistoryWithStepsAndEvents(id);
|
|
3001
|
-
}
|
|
3002
|
-
async deleteWorkflowHistoryWithRelated(id) {
|
|
3003
|
-
await this.initialized;
|
|
3004
|
-
return this.workflowExtension.deleteWorkflowHistoryWithRelated(id);
|
|
3005
|
-
}
|
|
3006
|
-
async cleanupOldWorkflowHistories(workflowId, maxEntries) {
|
|
3007
|
-
await this.initialized;
|
|
3008
|
-
return this.workflowExtension.cleanupOldWorkflowHistories(workflowId, maxEntries);
|
|
3009
|
-
}
|
|
3010
2066
|
/**
|
|
3011
|
-
* Get
|
|
2067
|
+
* Get statistics about the vector table and cache
|
|
3012
2068
|
*/
|
|
3013
|
-
|
|
3014
|
-
|
|
2069
|
+
async getStats() {
|
|
2070
|
+
await this.initialize();
|
|
2071
|
+
const tableName = `${this.tablePrefix}_vectors`;
|
|
2072
|
+
const [countResult, sizeResult] = await Promise.all([
|
|
2073
|
+
this.executeWithRetry(
|
|
2074
|
+
async () => await this.client.execute(
|
|
2075
|
+
`SELECT COUNT(*) as count, MAX(dimensions) as dims FROM ${tableName}`
|
|
2076
|
+
),
|
|
2077
|
+
"getStats count"
|
|
2078
|
+
),
|
|
2079
|
+
// Approximate table size by summing blob/text lengths
|
|
2080
|
+
this.executeWithRetry(
|
|
2081
|
+
async () => await this.client.execute({
|
|
2082
|
+
sql: `SELECT
|
|
2083
|
+
COALESCE(SUM(LENGTH(id)),0) +
|
|
2084
|
+
COALESCE(SUM(LENGTH(vector)),0) +
|
|
2085
|
+
COALESCE(SUM(LENGTH(metadata)),0) +
|
|
2086
|
+
COALESCE(SUM(LENGTH(content)),0) AS size
|
|
2087
|
+
FROM ${tableName}`
|
|
2088
|
+
}),
|
|
2089
|
+
"getStats size"
|
|
2090
|
+
)
|
|
2091
|
+
]);
|
|
2092
|
+
const row1 = countResult.rows[0];
|
|
2093
|
+
const row2 = sizeResult.rows[0];
|
|
2094
|
+
const countRaw = row1?.count;
|
|
2095
|
+
const dimsRaw = row1?.dims;
|
|
2096
|
+
const sizeRaw = row2?.size;
|
|
2097
|
+
const normalize = /* @__PURE__ */ __name((v) => typeof v === "bigint" ? Number(v) : typeof v === "string" ? Number.parseInt(v, 10) || 0 : v ?? 0, "normalize");
|
|
2098
|
+
return {
|
|
2099
|
+
count: normalize(countRaw),
|
|
2100
|
+
dimensions: dimsRaw != null ? normalize(dimsRaw) : this.dimensions,
|
|
2101
|
+
cacheSize: this.vectorCache.size,
|
|
2102
|
+
tableSizeBytes: normalize(sizeRaw)
|
|
2103
|
+
};
|
|
3015
2104
|
}
|
|
3016
2105
|
};
|
|
3017
2106
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3018
2107
|
0 && (module.exports = {
|
|
3019
|
-
|
|
2108
|
+
LibSQLMemoryAdapter,
|
|
2109
|
+
LibSQLObservabilityAdapter,
|
|
2110
|
+
LibSQLVectorAdapter
|
|
3020
2111
|
});
|
|
3021
2112
|
//# sourceMappingURL=index.js.map
|