@ixo/sqlite-saver 1.0.4
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/.eslintrc.js +9 -0
- package/.prettierignore +3 -0
- package/.prettierrc.js +4 -0
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +25 -0
- package/README.md +0 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +567 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/001_add_created_at_to_messages.d.ts +8 -0
- package/dist/migrations/001_add_created_at_to_messages.d.ts.map +1 -0
- package/dist/migrations/001_add_created_at_to_messages.js +32 -0
- package/dist/migrations/001_add_created_at_to_messages.js.map +1 -0
- package/dist/tests/agent-with-checkpoiner.test.d.ts +2 -0
- package/dist/tests/agent-with-checkpoiner.test.d.ts.map +1 -0
- package/dist/tests/agent-with-checkpoiner.test.js +206 -0
- package/dist/tests/agent-with-checkpoiner.test.js.map +1 -0
- package/dist/tests/checkpointer.test.d.ts +2 -0
- package/dist/tests/checkpointer.test.d.ts.map +1 -0
- package/dist/tests/checkpointer.test.js +426 -0
- package/dist/tests/checkpointer.test.js.map +1 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +284 -0
- package/dist/utils.js.map +1 -0
- package/jest.config.js +6 -0
- package/package.json +41 -0
- package/src/index.ts +929 -0
- package/src/migrations/001_add_created_at_to_messages.ts +48 -0
- package/src/tests/agent-with-checkpoiner.test.ts +264 -0
- package/src/tests/checkpointer.test.ts +628 -0
- package/src/utils.ts +358 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
package/.eslintrc.js
ADDED
package/.prettierignore
ADDED
package/.prettierrc.js
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @ixo/sqlite-saver
|
|
2
|
+
|
|
3
|
+
## 1.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`c643779`](https://github.com/ixoworld/companion/commit/c6437794acd28c833074763449502daf61e40a4c) Thanks [@youssefhany-ixo](https://github.com/youssefhany-ixo)! - matrix fix
|
|
8
|
+
|
|
9
|
+
## 1.0.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`b5799ee`](https://github.com/ixoworld/companion/commit/b5799ee19a0957ad38e2374ae18e11278295a1ab) Thanks [@youssefhany-ixo](https://github.com/youssefhany-ixo)! - Fix testnet signed mnemonics
|
|
14
|
+
|
|
15
|
+
## 1.0.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#84](https://github.com/ixoworld/companion/pull/84) [`3117e8d`](https://github.com/ixoworld/companion/commit/3117e8d2f753811511de4eda8e99b18c3888e083) Thanks [@youssefhany-ixo](https://github.com/youssefhany-ixo)! - update
|
|
20
|
+
|
|
21
|
+
## 1.0.1
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- [#79](https://github.com/ixoworld/companion/pull/79) [`0fe4fab`](https://github.com/ixoworld/companion/commit/0fe4fabaea19e081cec76e740c2e935a92eae338) Thanks [@youssefhany-ixo](https://github.com/youssefhany-ixo)! - Add Gzip -- optmizeDB by removing unused indexes -- make token limit deduct more in devnet for easy tesing
|
package/README.md
ADDED
|
File without changes
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
2
|
+
import { BaseCheckpointSaver, type Checkpoint, type CheckpointListOptions, type CheckpointMetadata, type CheckpointTuple, type PendingWrite, type SerializerProtocol } from '@langchain/langgraph-checkpoint';
|
|
3
|
+
import { Database as DatabaseType, Statement } from 'better-sqlite3';
|
|
4
|
+
interface Migration {
|
|
5
|
+
version: number;
|
|
6
|
+
name: string;
|
|
7
|
+
up: (db: DatabaseType) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare class SqliteSaver extends BaseCheckpointSaver {
|
|
10
|
+
db: DatabaseType;
|
|
11
|
+
protected isSetup: boolean;
|
|
12
|
+
protected withoutCheckpoint: Statement;
|
|
13
|
+
protected withCheckpoint: Statement;
|
|
14
|
+
protected putCheckpointStmt: Statement;
|
|
15
|
+
protected putWritesStmt: Statement;
|
|
16
|
+
protected deleteCheckpointsStmt: Statement;
|
|
17
|
+
protected putMessageStmt: Statement;
|
|
18
|
+
protected getMessageStmt: Statement;
|
|
19
|
+
protected deleteWritesStmt: Statement;
|
|
20
|
+
constructor(db: DatabaseType, serde?: SerializerProtocol);
|
|
21
|
+
static fromConnString(connStringOrLocalPath: string): SqliteSaver;
|
|
22
|
+
static fromDatabase(db: DatabaseType, serde?: SerializerProtocol): SqliteSaver;
|
|
23
|
+
close(): void;
|
|
24
|
+
protected createSchemaMigrationsTable(): void;
|
|
25
|
+
protected getAppliedMigrations(): number[];
|
|
26
|
+
protected recordMigration(migration: Migration): void;
|
|
27
|
+
protected loadMigrations(): Migration[];
|
|
28
|
+
protected runMigrations(): void;
|
|
29
|
+
protected setup(): void;
|
|
30
|
+
getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined>;
|
|
31
|
+
list(config: RunnableConfig, options?: CheckpointListOptions): AsyncGenerator<CheckpointTuple>;
|
|
32
|
+
put(config: RunnableConfig, _checkpoint: Checkpoint, metadata: CheckpointMetadata): Promise<RunnableConfig>;
|
|
33
|
+
putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string): Promise<void>;
|
|
34
|
+
deleteThread(threadId: string): Promise<void>;
|
|
35
|
+
protected migratePendingSends(checkpoint: Checkpoint, threadId: string, parentCheckpointId: string): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAGpB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EAExB,MAAM,iCAAiC,CAAC;AACzC,OAAiB,EAAE,QAAQ,IAAI,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAyD/E,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,CAAC;CAChC;AA+ED,qBAAa,WAAY,SAAQ,mBAAmB;IAClD,EAAE,EAAE,YAAY,CAAC;IAEjB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC;IAE3B,SAAS,CAAC,iBAAiB,EAAE,SAAS,CAAC;IAEvC,SAAS,CAAC,cAAc,EAAE,SAAS,CAAC;IAEpC,SAAS,CAAC,iBAAiB,EAAE,SAAS,CAAC;IAEvC,SAAS,CAAC,aAAa,EAAE,SAAS,CAAC;IAEnC,SAAS,CAAC,qBAAqB,EAAE,SAAS,CAAC;IAC3C,SAAS,CAAC,cAAc,EAAE,SAAS,CAAC;IAEpC,SAAS,CAAC,cAAc,EAAE,SAAS,CAAC;IAEpC,SAAS,CAAC,gBAAgB,EAAE,SAAS,CAAC;gBAE1B,EAAE,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,kBAAkB;IAMxD,MAAM,CAAC,cAAc,CAAC,qBAAqB,EAAE,MAAM,GAAG,WAAW;IAIjE,MAAM,CAAC,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,kBAAkB,GAAG,WAAW;IAI9E,KAAK,IAAI,IAAI;IASb,SAAS,CAAC,2BAA2B,IAAI,IAAI;IAa7C,SAAS,CAAC,oBAAoB,IAAI,MAAM,EAAE;IAe1C,SAAS,CAAC,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAerD,SAAS,CAAC,cAAc,IAAI,SAAS,EAAE;IAiBvC,SAAS,CAAC,aAAa,IAAI,IAAI;IAoC/B,SAAS,CAAC,KAAK,IAAI,IAAI;IA8GjB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAkGrE,IAAI,CACT,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,cAAc,CAAC,eAAe,CAAC;IAyK5B,GAAG,CACP,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,UAAU,EACvB,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,cAAc,CAAC;IA8GpB,SAAS,CACb,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,YAAY,EAAE,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC;IA8DV,YAAY,CAAC,QAAQ,EAAE,MAAM;cASnB,mBAAmB,CACjC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,MAAM;CAsC7B"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SqliteSaver = void 0;
|
|
7
|
+
const langgraph_checkpoint_1 = require("@langchain/langgraph-checkpoint");
|
|
8
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
9
|
+
const _001_add_created_at_to_messages_1 = __importDefault(require("./migrations/001_add_created_at_to_messages"));
|
|
10
|
+
const utils_1 = require("./utils");
|
|
11
|
+
const checkpointMetadataKeys = ['source', 'step', 'parents'];
|
|
12
|
+
function validateKeys(keys) {
|
|
13
|
+
return keys;
|
|
14
|
+
}
|
|
15
|
+
const validCheckpointMetadataKeys = validateKeys(checkpointMetadataKeys);
|
|
16
|
+
function prepareSql(db, checkpointId) {
|
|
17
|
+
const sql = `
|
|
18
|
+
SELECT
|
|
19
|
+
thread_id,
|
|
20
|
+
checkpoint_ns,
|
|
21
|
+
checkpoint_id,
|
|
22
|
+
parent_checkpoint_id,
|
|
23
|
+
type,
|
|
24
|
+
checkpoint,
|
|
25
|
+
metadata,
|
|
26
|
+
(
|
|
27
|
+
SELECT
|
|
28
|
+
json_group_array(
|
|
29
|
+
json_object(
|
|
30
|
+
'task_id', pw.task_id,
|
|
31
|
+
'channel', pw.channel,
|
|
32
|
+
'type', pw.type,
|
|
33
|
+
'value', CAST(pw.value AS TEXT)
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
FROM writes as pw
|
|
37
|
+
WHERE pw.thread_id = checkpoints.thread_id
|
|
38
|
+
AND pw.checkpoint_ns = checkpoints.checkpoint_ns
|
|
39
|
+
AND pw.checkpoint_id = checkpoints.checkpoint_id
|
|
40
|
+
) as pending_writes,
|
|
41
|
+
(
|
|
42
|
+
SELECT
|
|
43
|
+
json_group_array(
|
|
44
|
+
json_object(
|
|
45
|
+
'type', ps.type,
|
|
46
|
+
'value', CAST(ps.value AS TEXT)
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
FROM writes as ps
|
|
50
|
+
WHERE ps.thread_id = checkpoints.thread_id
|
|
51
|
+
AND ps.checkpoint_ns = checkpoints.checkpoint_ns
|
|
52
|
+
AND ps.checkpoint_id = checkpoints.parent_checkpoint_id
|
|
53
|
+
AND ps.channel = '${langgraph_checkpoint_1.TASKS}'
|
|
54
|
+
ORDER BY ps.idx
|
|
55
|
+
) as pending_sends
|
|
56
|
+
FROM checkpoints
|
|
57
|
+
WHERE thread_id = ? AND checkpoint_ns = ? ${checkpointId
|
|
58
|
+
? 'AND checkpoint_id = ?'
|
|
59
|
+
: 'ORDER BY checkpoint_id DESC LIMIT 1'}`;
|
|
60
|
+
return db.prepare(sql);
|
|
61
|
+
}
|
|
62
|
+
class SqliteSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
63
|
+
db;
|
|
64
|
+
isSetup;
|
|
65
|
+
withoutCheckpoint;
|
|
66
|
+
withCheckpoint;
|
|
67
|
+
putCheckpointStmt;
|
|
68
|
+
putWritesStmt;
|
|
69
|
+
deleteCheckpointsStmt;
|
|
70
|
+
putMessageStmt;
|
|
71
|
+
getMessageStmt;
|
|
72
|
+
deleteWritesStmt;
|
|
73
|
+
constructor(db, serde) {
|
|
74
|
+
super(serde);
|
|
75
|
+
this.db = db;
|
|
76
|
+
this.isSetup = false;
|
|
77
|
+
}
|
|
78
|
+
static fromConnString(connStringOrLocalPath) {
|
|
79
|
+
return new SqliteSaver(new better_sqlite3_1.default(connStringOrLocalPath));
|
|
80
|
+
}
|
|
81
|
+
static fromDatabase(db, serde) {
|
|
82
|
+
return new SqliteSaver(db, serde);
|
|
83
|
+
}
|
|
84
|
+
close() {
|
|
85
|
+
if (this.db.open) {
|
|
86
|
+
this.db.close();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
createSchemaMigrationsTable() {
|
|
90
|
+
this.db.exec(`
|
|
91
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
92
|
+
version INTEGER NOT NULL PRIMARY KEY,
|
|
93
|
+
name TEXT NOT NULL,
|
|
94
|
+
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
95
|
+
);
|
|
96
|
+
`);
|
|
97
|
+
}
|
|
98
|
+
getAppliedMigrations() {
|
|
99
|
+
try {
|
|
100
|
+
const rows = this.db
|
|
101
|
+
.prepare('SELECT version FROM schema_migrations ORDER BY version')
|
|
102
|
+
.all();
|
|
103
|
+
return rows.map((row) => row.version);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
recordMigration(migration) {
|
|
110
|
+
this.db
|
|
111
|
+
.prepare('INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, CURRENT_TIMESTAMP)')
|
|
112
|
+
.run(migration.version, migration.name);
|
|
113
|
+
}
|
|
114
|
+
loadMigrations() {
|
|
115
|
+
const migrations = [
|
|
116
|
+
_001_add_created_at_to_messages_1.default,
|
|
117
|
+
];
|
|
118
|
+
migrations.sort((a, b) => a.version - b.version);
|
|
119
|
+
return migrations;
|
|
120
|
+
}
|
|
121
|
+
runMigrations() {
|
|
122
|
+
this.createSchemaMigrationsTable();
|
|
123
|
+
const appliedVersions = this.getAppliedMigrations();
|
|
124
|
+
const allMigrations = this.loadMigrations();
|
|
125
|
+
const pendingMigrations = allMigrations.filter((migration) => !appliedVersions.includes(migration.version));
|
|
126
|
+
if (pendingMigrations.length === 0) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
console.log(`Running ${pendingMigrations.length} pending migration(s)...`);
|
|
130
|
+
for (const migration of pendingMigrations) {
|
|
131
|
+
try {
|
|
132
|
+
console.log(`Applying migration ${migration.version}: ${migration.name}`);
|
|
133
|
+
migration.up(this.db);
|
|
134
|
+
this.recordMigration(migration);
|
|
135
|
+
console.log(`✓ Migration ${migration.version}: ${migration.name} applied successfully`);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.error(`✗ Failed to apply migration ${migration.version}: ${migration.name}`, error);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
setup() {
|
|
144
|
+
if (this.isSetup) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.db.pragma('journal_mode = WAL');
|
|
148
|
+
this.db.pragma('busy_timeout = 5000');
|
|
149
|
+
this.db.exec(`
|
|
150
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
151
|
+
thread_id TEXT NOT NULL,
|
|
152
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
153
|
+
checkpoint_id TEXT NOT NULL,
|
|
154
|
+
parent_checkpoint_id TEXT,
|
|
155
|
+
type TEXT,
|
|
156
|
+
checkpoint BLOB,
|
|
157
|
+
metadata BLOB,
|
|
158
|
+
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)
|
|
159
|
+
);`);
|
|
160
|
+
this.db.exec(`
|
|
161
|
+
CREATE TABLE IF NOT EXISTS writes (
|
|
162
|
+
thread_id TEXT NOT NULL,
|
|
163
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
164
|
+
checkpoint_id TEXT NOT NULL,
|
|
165
|
+
task_id TEXT NOT NULL,
|
|
166
|
+
idx INTEGER NOT NULL,
|
|
167
|
+
channel TEXT NOT NULL,
|
|
168
|
+
type TEXT,
|
|
169
|
+
value BLOB,
|
|
170
|
+
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
|
|
171
|
+
);`);
|
|
172
|
+
this.db.exec(`
|
|
173
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
174
|
+
thread_id TEXT NOT NULL,
|
|
175
|
+
checkpoint_ns TEXT NOT NULL DEFAULT '',
|
|
176
|
+
checkpoint_id TEXT NOT NULL,
|
|
177
|
+
message_id TEXT NOT NULL,
|
|
178
|
+
message_type TEXT NOT NULL,
|
|
179
|
+
message_content TEXT NOT NULL,
|
|
180
|
+
message BLOB,
|
|
181
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
182
|
+
PRIMARY KEY (message_id)
|
|
183
|
+
);
|
|
184
|
+
`);
|
|
185
|
+
this.runMigrations();
|
|
186
|
+
this.db.exec(`
|
|
187
|
+
CREATE INDEX IF NOT EXISTS idx_messages_thread_id
|
|
188
|
+
ON messages(thread_id);
|
|
189
|
+
`);
|
|
190
|
+
this.db.exec(`
|
|
191
|
+
CREATE INDEX IF NOT EXISTS idx_messages_checkpoint_id
|
|
192
|
+
ON messages(checkpoint_id);
|
|
193
|
+
`);
|
|
194
|
+
this.db.exec(`
|
|
195
|
+
CREATE INDEX IF NOT EXISTS idx_messages_lookup
|
|
196
|
+
ON messages(thread_id, checkpoint_ns, checkpoint_id);
|
|
197
|
+
`);
|
|
198
|
+
this.db.exec(`
|
|
199
|
+
CREATE INDEX IF NOT EXISTS idx_messages_thread_created
|
|
200
|
+
ON messages(thread_id, created_at);
|
|
201
|
+
`);
|
|
202
|
+
this.db.exec(`
|
|
203
|
+
CREATE INDEX IF NOT EXISTS idx_writes_channel
|
|
204
|
+
ON writes(thread_id, checkpoint_id, channel);
|
|
205
|
+
`);
|
|
206
|
+
this.withoutCheckpoint = prepareSql(this.db, false);
|
|
207
|
+
this.withCheckpoint = prepareSql(this.db, true);
|
|
208
|
+
this.putCheckpointStmt = this.db.prepare(`INSERT OR REPLACE INTO checkpoints (thread_id, checkpoint_ns, checkpoint_id, parent_checkpoint_id, type, checkpoint, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
209
|
+
this.putWritesStmt = this.db.prepare(`
|
|
210
|
+
INSERT OR REPLACE INTO writes
|
|
211
|
+
(thread_id, checkpoint_ns, checkpoint_id, task_id, idx, channel, type, value)
|
|
212
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
213
|
+
`);
|
|
214
|
+
this.deleteCheckpointsStmt = this.db.prepare(`DELETE FROM checkpoints WHERE thread_id = ?`);
|
|
215
|
+
this.deleteWritesStmt = this.db.prepare(`DELETE FROM writes WHERE thread_id = ?`);
|
|
216
|
+
this.putMessageStmt = this.db.prepare(`INSERT OR REPLACE INTO messages (thread_id, checkpoint_ns, checkpoint_id, message_id, message_type, message_content, message, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
217
|
+
this.getMessageStmt = this.db.prepare(`SELECT * FROM messages WHERE thread_id = ? AND checkpoint_ns = ? AND checkpoint_id = ?`);
|
|
218
|
+
this.isSetup = true;
|
|
219
|
+
}
|
|
220
|
+
async getTuple(config) {
|
|
221
|
+
this.setup();
|
|
222
|
+
const { thread_id, checkpoint_ns = '', checkpoint_id, } = config.configurable ?? {};
|
|
223
|
+
const args = [thread_id, checkpoint_ns];
|
|
224
|
+
if (checkpoint_id)
|
|
225
|
+
args.push(checkpoint_id);
|
|
226
|
+
const stm = checkpoint_id ? this.withCheckpoint : this.withoutCheckpoint;
|
|
227
|
+
const row = stm.get(...args);
|
|
228
|
+
if (row === undefined)
|
|
229
|
+
return undefined;
|
|
230
|
+
let finalConfig = config;
|
|
231
|
+
if (!checkpoint_id) {
|
|
232
|
+
finalConfig = {
|
|
233
|
+
configurable: {
|
|
234
|
+
thread_id: row.thread_id,
|
|
235
|
+
checkpoint_ns,
|
|
236
|
+
checkpoint_id: row.checkpoint_id,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
if (finalConfig.configurable?.thread_id === undefined ||
|
|
241
|
+
finalConfig.configurable?.checkpoint_id === undefined) {
|
|
242
|
+
throw new Error('Missing thread_id or checkpoint_id');
|
|
243
|
+
}
|
|
244
|
+
const messages = this.getMessageStmt.all(finalConfig.configurable?.thread_id, finalConfig.configurable?.checkpoint_ns, finalConfig.configurable?.checkpoint_id);
|
|
245
|
+
const pendingWrites = await Promise.all(JSON.parse(row.pending_writes).map(async (write) => {
|
|
246
|
+
return [
|
|
247
|
+
write.task_id,
|
|
248
|
+
write.channel,
|
|
249
|
+
await this.serde.loadsTyped(write.type ?? 'json', write.value ?? ''),
|
|
250
|
+
];
|
|
251
|
+
}));
|
|
252
|
+
const parsedMessages = await Promise.all(messages.map(async (message) => {
|
|
253
|
+
return this.serde.loadsTyped('json', message.message);
|
|
254
|
+
}));
|
|
255
|
+
const checkpoint = (await this.serde.loadsTyped(row.type ?? 'json', row.checkpoint));
|
|
256
|
+
if (parsedMessages.length > 0) {
|
|
257
|
+
checkpoint.channel_values.messages = parsedMessages;
|
|
258
|
+
}
|
|
259
|
+
if (checkpoint.v < 4 && row.parent_checkpoint_id != null) {
|
|
260
|
+
await this.migratePendingSends(checkpoint, row.thread_id, row.parent_checkpoint_id);
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
checkpoint,
|
|
264
|
+
config: finalConfig,
|
|
265
|
+
metadata: (await this.serde.loadsTyped(row.type ?? 'json', row.metadata)),
|
|
266
|
+
parentConfig: row.parent_checkpoint_id
|
|
267
|
+
? {
|
|
268
|
+
configurable: {
|
|
269
|
+
thread_id: row.thread_id,
|
|
270
|
+
checkpoint_ns,
|
|
271
|
+
checkpoint_id: row.parent_checkpoint_id,
|
|
272
|
+
},
|
|
273
|
+
}
|
|
274
|
+
: undefined,
|
|
275
|
+
pendingWrites,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
async *list(config, options) {
|
|
279
|
+
const { limit, before, filter } = options ?? {};
|
|
280
|
+
this.setup();
|
|
281
|
+
const thread_id = config.configurable?.thread_id;
|
|
282
|
+
const checkpoint_ns = config.configurable?.checkpoint_ns;
|
|
283
|
+
let sql = `
|
|
284
|
+
SELECT
|
|
285
|
+
thread_id,
|
|
286
|
+
checkpoint_ns,
|
|
287
|
+
checkpoint_id,
|
|
288
|
+
parent_checkpoint_id,
|
|
289
|
+
type,
|
|
290
|
+
checkpoint,
|
|
291
|
+
metadata,
|
|
292
|
+
(
|
|
293
|
+
SELECT
|
|
294
|
+
json_group_array(
|
|
295
|
+
json_object(
|
|
296
|
+
'task_id', pw.task_id,
|
|
297
|
+
'channel', pw.channel,
|
|
298
|
+
'type', pw.type,
|
|
299
|
+
'value', CAST(pw.value AS TEXT)
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
FROM writes as pw
|
|
303
|
+
WHERE pw.thread_id = checkpoints.thread_id
|
|
304
|
+
AND pw.checkpoint_ns = checkpoints.checkpoint_ns
|
|
305
|
+
AND pw.checkpoint_id = checkpoints.checkpoint_id
|
|
306
|
+
) as pending_writes,
|
|
307
|
+
(
|
|
308
|
+
SELECT
|
|
309
|
+
json_group_array(
|
|
310
|
+
json_object(
|
|
311
|
+
'type', ps.type,
|
|
312
|
+
'value', CAST(ps.value AS TEXT)
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
FROM writes as ps
|
|
316
|
+
WHERE ps.thread_id = checkpoints.thread_id
|
|
317
|
+
AND ps.checkpoint_ns = checkpoints.checkpoint_ns
|
|
318
|
+
AND ps.checkpoint_id = checkpoints.parent_checkpoint_id
|
|
319
|
+
AND ps.channel = '${langgraph_checkpoint_1.TASKS}'
|
|
320
|
+
ORDER BY ps.idx
|
|
321
|
+
) as pending_sends
|
|
322
|
+
FROM checkpoints\n`;
|
|
323
|
+
const whereClause = [];
|
|
324
|
+
if (thread_id) {
|
|
325
|
+
whereClause.push('thread_id = ?');
|
|
326
|
+
}
|
|
327
|
+
if (checkpoint_ns !== undefined && checkpoint_ns !== null) {
|
|
328
|
+
whereClause.push('checkpoint_ns = ?');
|
|
329
|
+
}
|
|
330
|
+
if (before?.configurable?.checkpoint_id !== undefined) {
|
|
331
|
+
whereClause.push('checkpoint_id < ?');
|
|
332
|
+
}
|
|
333
|
+
const sanitizedFilter = Object.fromEntries(Object.entries(filter ?? {}).filter(([key, value]) => value !== undefined &&
|
|
334
|
+
validCheckpointMetadataKeys.includes(key)));
|
|
335
|
+
whereClause.push(...Object.entries(sanitizedFilter).map(([key]) => `jsonb(CAST(metadata AS TEXT))->'$.${key}' = ?`));
|
|
336
|
+
if (whereClause.length > 0) {
|
|
337
|
+
sql += `WHERE\n ${whereClause.join(' AND\n ')}\n`;
|
|
338
|
+
}
|
|
339
|
+
sql += '\nORDER BY checkpoint_id DESC';
|
|
340
|
+
if (limit) {
|
|
341
|
+
sql += ` LIMIT ${parseInt(limit, 10)}`;
|
|
342
|
+
}
|
|
343
|
+
const args = [
|
|
344
|
+
thread_id,
|
|
345
|
+
checkpoint_ns,
|
|
346
|
+
before?.configurable?.checkpoint_id,
|
|
347
|
+
...Object.values(sanitizedFilter).map((value) => JSON.stringify(value)),
|
|
348
|
+
].filter((value) => value !== undefined && value !== null);
|
|
349
|
+
const rows = this.db
|
|
350
|
+
.prepare(sql)
|
|
351
|
+
.all(...args);
|
|
352
|
+
if (rows) {
|
|
353
|
+
for (const row of rows) {
|
|
354
|
+
const pendingWrites = await Promise.all(JSON.parse(row.pending_writes).map(async (write) => {
|
|
355
|
+
return [
|
|
356
|
+
write.task_id,
|
|
357
|
+
write.channel,
|
|
358
|
+
await this.serde.loadsTyped(write.type ?? 'json', write.value ?? ''),
|
|
359
|
+
];
|
|
360
|
+
}));
|
|
361
|
+
const messages = this.getMessageStmt.all(row.thread_id, row.checkpoint_ns, row.checkpoint_id);
|
|
362
|
+
const parsedMessages = await Promise.all(messages.map(async (message) => {
|
|
363
|
+
return this.serde.loadsTyped('json', message.message);
|
|
364
|
+
}));
|
|
365
|
+
const checkpoint = (await this.serde.loadsTyped(row.type ?? 'json', row.checkpoint));
|
|
366
|
+
if (parsedMessages.length > 0) {
|
|
367
|
+
checkpoint.channel_values.messages = parsedMessages;
|
|
368
|
+
}
|
|
369
|
+
if (checkpoint.v < 4 && row.parent_checkpoint_id != null) {
|
|
370
|
+
await this.migratePendingSends(checkpoint, row.thread_id, row.parent_checkpoint_id);
|
|
371
|
+
}
|
|
372
|
+
yield {
|
|
373
|
+
config: {
|
|
374
|
+
configurable: {
|
|
375
|
+
thread_id: row.thread_id,
|
|
376
|
+
checkpoint_ns: row.checkpoint_ns,
|
|
377
|
+
checkpoint_id: row.checkpoint_id,
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
checkpoint,
|
|
381
|
+
metadata: (await this.serde.loadsTyped(row.type ?? 'json', row.metadata)),
|
|
382
|
+
parentConfig: row.parent_checkpoint_id
|
|
383
|
+
? {
|
|
384
|
+
configurable: {
|
|
385
|
+
thread_id: row.thread_id,
|
|
386
|
+
checkpoint_ns: row.checkpoint_ns,
|
|
387
|
+
checkpoint_id: row.parent_checkpoint_id,
|
|
388
|
+
},
|
|
389
|
+
}
|
|
390
|
+
: undefined,
|
|
391
|
+
pendingWrites,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async put(config, _checkpoint, metadata) {
|
|
397
|
+
this.setup();
|
|
398
|
+
if (!config.configurable) {
|
|
399
|
+
throw new Error('Empty configuration supplied.');
|
|
400
|
+
}
|
|
401
|
+
const thread_id = config.configurable?.thread_id;
|
|
402
|
+
const checkpoint_ns = config.configurable?.checkpoint_ns ?? '';
|
|
403
|
+
const parent_checkpoint_id = config.configurable?.checkpoint_id;
|
|
404
|
+
if (!thread_id) {
|
|
405
|
+
throw new Error(`Missing "thread_id" field in passed "config.configurable".`);
|
|
406
|
+
}
|
|
407
|
+
const { checkpoint, messages } = removeMessagesFromCheckpoint(_checkpoint);
|
|
408
|
+
const [[type1, serializedCheckpoint], [type2, serializedMetadata]] = await Promise.all([
|
|
409
|
+
this.serde.dumpsTyped(checkpoint),
|
|
410
|
+
this.serde.dumpsTyped(metadata),
|
|
411
|
+
]);
|
|
412
|
+
if (type1 !== type2) {
|
|
413
|
+
throw new Error('Failed to serialized checkpoint and metadata to the same type.');
|
|
414
|
+
}
|
|
415
|
+
const row = [
|
|
416
|
+
thread_id,
|
|
417
|
+
checkpoint_ns,
|
|
418
|
+
checkpoint.id,
|
|
419
|
+
parent_checkpoint_id,
|
|
420
|
+
type1,
|
|
421
|
+
serializedCheckpoint,
|
|
422
|
+
serializedMetadata,
|
|
423
|
+
];
|
|
424
|
+
const transaction = this.db.transaction(() => {
|
|
425
|
+
this.putCheckpointStmt.run(...row);
|
|
426
|
+
if (messages) {
|
|
427
|
+
for (const message of messages) {
|
|
428
|
+
const encoder = new TextEncoder();
|
|
429
|
+
const msgFromMatrixRoom = message.additional_kwargs
|
|
430
|
+
.msgFromMatrixRoom;
|
|
431
|
+
const _additionalKwargs = message.additional_kwargs;
|
|
432
|
+
const cleanedAdditionalKwargs = (0, utils_1.cleanAdditionalKwargs)(message.additional_kwargs, msgFromMatrixRoom ?? false);
|
|
433
|
+
message.additional_kwargs = {
|
|
434
|
+
...cleanedAdditionalKwargs,
|
|
435
|
+
reasoning: cleanedAdditionalKwargs.reasoning ?? _additionalKwargs.reasoning,
|
|
436
|
+
reasoningDetails: cleanedAdditionalKwargs.reasoningDetails ??
|
|
437
|
+
_additionalKwargs.reasoningDetails,
|
|
438
|
+
};
|
|
439
|
+
if (message.type !== 'ai') {
|
|
440
|
+
delete message.additional_kwargs.reasoning;
|
|
441
|
+
delete message.additional_kwargs.reasoningDetails;
|
|
442
|
+
}
|
|
443
|
+
const serializedMessage = encoder.encode((0, utils_1.stringify)(message, (_, value) => {
|
|
444
|
+
return (0, utils_1._default)(value);
|
|
445
|
+
}));
|
|
446
|
+
const messageRow = {
|
|
447
|
+
thread_id,
|
|
448
|
+
checkpoint_ns,
|
|
449
|
+
checkpoint_id: checkpoint.id,
|
|
450
|
+
message_id: message.id ?? message.lc_kwargs?.id,
|
|
451
|
+
message_type: message.type,
|
|
452
|
+
message_content: message.content.toString(),
|
|
453
|
+
message: serializedMessage,
|
|
454
|
+
created_at: message.additional_kwargs?.timestamp ??
|
|
455
|
+
new Date().toISOString(),
|
|
456
|
+
};
|
|
457
|
+
this.putMessageStmt.run(messageRow.thread_id, messageRow.checkpoint_ns, messageRow.checkpoint_id, messageRow.message_id, messageRow.message_type, messageRow.message_content, messageRow.message, messageRow.created_at);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
transaction();
|
|
462
|
+
return {
|
|
463
|
+
configurable: {
|
|
464
|
+
thread_id,
|
|
465
|
+
checkpoint_ns,
|
|
466
|
+
checkpoint_id: checkpoint.id,
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
async putWrites(config, writes, taskId) {
|
|
471
|
+
this.setup();
|
|
472
|
+
if (!config.configurable) {
|
|
473
|
+
throw new Error('Empty configuration supplied.');
|
|
474
|
+
}
|
|
475
|
+
if (!config.configurable?.thread_id) {
|
|
476
|
+
console.error('Missing thread_id field in config.configurable.', {
|
|
477
|
+
configurable: config.configurable,
|
|
478
|
+
});
|
|
479
|
+
const threadId = this.db
|
|
480
|
+
.prepare('SELECT thread_id FROM checkpoints WHERE checkpoint_id = ?')
|
|
481
|
+
.get(config.configurable?.checkpoint_id);
|
|
482
|
+
if (!threadId) {
|
|
483
|
+
throw new Error('Missing thread_id field in config.configurable. config: ' +
|
|
484
|
+
JSON.stringify(config.configurable));
|
|
485
|
+
}
|
|
486
|
+
config.configurable.thread_id = threadId.thread_id;
|
|
487
|
+
}
|
|
488
|
+
if (!config.configurable?.checkpoint_id) {
|
|
489
|
+
console.error('Missing checkpoint_id field in config.configurable.', {
|
|
490
|
+
configurable: config.configurable,
|
|
491
|
+
});
|
|
492
|
+
throw new Error('Missing checkpoint_id field in config.configurable. config: ' +
|
|
493
|
+
JSON.stringify(config.configurable));
|
|
494
|
+
}
|
|
495
|
+
const transaction = this.db.transaction((rows) => {
|
|
496
|
+
for (const row of rows) {
|
|
497
|
+
this.putWritesStmt.run(...row);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
const rows = await Promise.all(writes.map(async (write, idx) => {
|
|
501
|
+
const [type, serializedWrite] = await this.serde.dumpsTyped(write[1]);
|
|
502
|
+
return [
|
|
503
|
+
config.configurable?.thread_id,
|
|
504
|
+
config.configurable?.checkpoint_ns,
|
|
505
|
+
config.configurable?.checkpoint_id,
|
|
506
|
+
taskId,
|
|
507
|
+
idx,
|
|
508
|
+
write[0],
|
|
509
|
+
type,
|
|
510
|
+
serializedWrite,
|
|
511
|
+
];
|
|
512
|
+
}));
|
|
513
|
+
transaction(rows);
|
|
514
|
+
}
|
|
515
|
+
async deleteThread(threadId) {
|
|
516
|
+
const transaction = this.db.transaction(() => {
|
|
517
|
+
this.deleteCheckpointsStmt.run(threadId);
|
|
518
|
+
this.deleteWritesStmt.run(threadId);
|
|
519
|
+
});
|
|
520
|
+
transaction();
|
|
521
|
+
}
|
|
522
|
+
async migratePendingSends(checkpoint, threadId, parentCheckpointId) {
|
|
523
|
+
const { pending_sends } = this.db
|
|
524
|
+
.prepare(`
|
|
525
|
+
SELECT
|
|
526
|
+
checkpoint_id,
|
|
527
|
+
json_group_array(
|
|
528
|
+
json_object(
|
|
529
|
+
'type', ps.type,
|
|
530
|
+
'value', CAST(ps.value AS TEXT)
|
|
531
|
+
)
|
|
532
|
+
) as pending_sends
|
|
533
|
+
FROM writes as ps
|
|
534
|
+
WHERE ps.thread_id = ?
|
|
535
|
+
AND ps.checkpoint_id = ?
|
|
536
|
+
AND ps.channel = '${langgraph_checkpoint_1.TASKS}'
|
|
537
|
+
ORDER BY ps.idx
|
|
538
|
+
`)
|
|
539
|
+
.get(threadId, parentCheckpointId);
|
|
540
|
+
const mutableCheckpoint = checkpoint;
|
|
541
|
+
mutableCheckpoint.channel_values ??= {};
|
|
542
|
+
mutableCheckpoint.channel_values[langgraph_checkpoint_1.TASKS] = await Promise.all(JSON.parse(pending_sends).map(({ type, value }) => this.serde.loadsTyped(type, value)));
|
|
543
|
+
mutableCheckpoint.channel_versions[langgraph_checkpoint_1.TASKS] =
|
|
544
|
+
Object.keys(checkpoint.channel_versions).length > 0
|
|
545
|
+
? (0, langgraph_checkpoint_1.maxChannelVersion)(...Object.values(checkpoint.channel_versions))
|
|
546
|
+
: this.getNextVersion(undefined);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
exports.SqliteSaver = SqliteSaver;
|
|
550
|
+
const isCheckpointWithMessages = (checkpoint) => {
|
|
551
|
+
return 'messages' in checkpoint.channel_values;
|
|
552
|
+
};
|
|
553
|
+
const removeMessagesFromCheckpoint = (checkpoint) => {
|
|
554
|
+
if (isCheckpointWithMessages(checkpoint)) {
|
|
555
|
+
const newCheckpoint = (0, langgraph_checkpoint_1.copyCheckpoint)(checkpoint);
|
|
556
|
+
delete newCheckpoint.channel_values.messages;
|
|
557
|
+
return {
|
|
558
|
+
checkpoint: newCheckpoint,
|
|
559
|
+
messages: checkpoint.channel_values.messages,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
checkpoint,
|
|
564
|
+
messages: undefined,
|
|
565
|
+
};
|
|
566
|
+
};
|
|
567
|
+
//# sourceMappingURL=index.js.map
|