@huyooo/ai-chat-storage 0.2.13 → 0.2.14

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.
@@ -28,6 +28,8 @@ var messages = sqliteTable("messages", {
28
28
  userId: text("user_id"),
29
29
  role: text("role").notNull(),
30
30
  content: text("content").notNull(),
31
+ /** 用户消息附带的图片 JSON 数组 */
32
+ images: text("images"),
31
33
  /** 生成此消息时使用的模型 */
32
34
  model: text("model"),
33
35
  /** 生成此消息时使用的模式 (ask/agent) */
@@ -39,10 +41,13 @@ var messages = sqliteTable("messages", {
39
41
  /** 执行步骤列表 JSON */
40
42
  steps: text("steps"),
41
43
  operationIds: text("operation_ids"),
44
+ /** 消息序号(会话内递增,用于分叉删除,必须) */
45
+ sequence: integer("sequence").notNull(),
42
46
  timestamp: integer("timestamp", { mode: "timestamp" }).notNull()
43
47
  }, (table) => [
44
48
  index("idx_messages_session").on(table.sessionId),
45
- index("idx_messages_tenant").on(table.appId, table.userId)
49
+ index("idx_messages_tenant").on(table.appId, table.userId),
50
+ index("idx_messages_sequence").on(table.sessionId, table.sequence)
46
51
  ]);
47
52
  var operations = sqliteTable("operations", {
48
53
  id: text("id").primaryKey(),
@@ -130,6 +135,7 @@ function initSchema(sqlite) {
130
135
  user_id TEXT,
131
136
  role TEXT NOT NULL,
132
137
  content TEXT NOT NULL,
138
+ images TEXT,
133
139
  model TEXT,
134
140
  mode TEXT,
135
141
  web_search_enabled INTEGER,
@@ -247,6 +253,55 @@ function initSchema(sqlite) {
247
253
  sqlite.exec(`ALTER TABLE messages ADD COLUMN steps TEXT`);
248
254
  } catch {
249
255
  }
256
+ try {
257
+ sqlite.exec(`ALTER TABLE messages ADD COLUMN sequence INTEGER`);
258
+ } catch {
259
+ }
260
+ try {
261
+ sqlite.exec(`ALTER TABLE messages ADD COLUMN images TEXT`);
262
+ } catch {
263
+ }
264
+ try {
265
+ sqlite.exec(`
266
+ UPDATE messages
267
+ SET sequence = (
268
+ SELECT COUNT(*) + 1
269
+ FROM messages m2
270
+ WHERE m2.session_id = messages.session_id
271
+ AND (m2.timestamp < messages.timestamp
272
+ OR (m2.timestamp = messages.timestamp AND m2.id < messages.id))
273
+ )
274
+ WHERE sequence IS NULL
275
+ `);
276
+ } catch {
277
+ }
278
+ try {
279
+ sqlite.exec(`
280
+ UPDATE messages
281
+ SET sequence = (
282
+ SELECT COUNT(*) + 1
283
+ FROM messages m2
284
+ WHERE m2.session_id = messages.session_id
285
+ AND (m2.timestamp < messages.timestamp
286
+ OR (m2.timestamp = messages.timestamp AND m2.id < messages.id))
287
+ )
288
+ WHERE sequence IS NULL
289
+ `);
290
+ sqlite.exec(`
291
+ UPDATE messages
292
+ SET sequence = (
293
+ SELECT COALESCE(MAX(sequence), 0) + 1
294
+ FROM messages m2
295
+ WHERE m2.session_id = messages.session_id
296
+ )
297
+ WHERE sequence IS NULL
298
+ `);
299
+ } catch {
300
+ }
301
+ try {
302
+ sqlite.exec(`CREATE INDEX IF NOT EXISTS idx_messages_sequence ON messages(session_id, sequence)`);
303
+ } catch {
304
+ }
250
305
  }
251
306
  function buildSessionsTenantCondition(ctx) {
252
307
  const conditions = [];
@@ -363,8 +418,41 @@ function deleteSession(db, id, ctx) {
363
418
  }
364
419
 
365
420
  // src/adapters/sqlite/messages.ts
366
- import { eq as eq3, and as and3, gt } from "drizzle-orm";
421
+ import { eq as eq3, and as and3, gt, max } from "drizzle-orm";
367
422
  function toMessageRecord(row) {
423
+ if (row.sequence === null || row.sequence === void 0) {
424
+ throw new Error(`\u6D88\u606F ${row.id} \u7F3A\u5C11\u5E8F\u53F7\uFF0C\u6570\u636E\u635F\u574F`);
425
+ }
426
+ const toSequence = (seq) => {
427
+ if (typeof seq === "number" && Number.isFinite(seq)) return seq;
428
+ if (typeof seq === "string") {
429
+ const parsed = Number(seq);
430
+ if (Number.isFinite(parsed)) return parsed;
431
+ }
432
+ throw new Error(`\u6D88\u606F ${row.id} \u7684\u5E8F\u53F7\u65E0\u6548: ${seq}`);
433
+ };
434
+ const toTimestamp = (ts) => {
435
+ if (typeof ts === "number" && Number.isFinite(ts)) return ts;
436
+ if (ts instanceof Date) return ts.getTime();
437
+ if (typeof ts === "string") {
438
+ const parsed = Date.parse(ts);
439
+ if (!Number.isNaN(parsed)) return parsed;
440
+ const asNum = Number(ts);
441
+ if (Number.isFinite(asNum)) return asNum;
442
+ }
443
+ throw new Error(`\u6D88\u606F ${row.id} \u7684\u65F6\u95F4\u6233\u65E0\u6548: ${ts}`);
444
+ };
445
+ let images = [];
446
+ if (row.images) {
447
+ try {
448
+ const parsed = JSON.parse(row.images);
449
+ if (Array.isArray(parsed)) {
450
+ images = parsed;
451
+ }
452
+ } catch {
453
+ console.warn(`\u6D88\u606F ${row.id} \u7684 images \u5B57\u6BB5\u89E3\u6790\u5931\u8D25:`, row.images);
454
+ }
455
+ }
368
456
  return {
369
457
  id: row.id,
370
458
  sessionId: row.sessionId,
@@ -372,19 +460,26 @@ function toMessageRecord(row) {
372
460
  userId: row.userId,
373
461
  role: row.role,
374
462
  content: row.content,
463
+ images,
375
464
  model: row.model,
376
465
  mode: row.mode,
466
+ // Drizzle ORM 的 mode: 'boolean' 已自动处理类型转换
377
467
  webSearchEnabled: row.webSearchEnabled,
378
468
  thinkingEnabled: row.thinkingEnabled,
379
469
  steps: row.steps,
380
470
  operationIds: row.operationIds,
381
- timestamp: row.timestamp
471
+ sequence: toSequence(row.sequence),
472
+ timestamp: toTimestamp(row.timestamp)
382
473
  };
383
474
  }
384
475
  function getMessages(db, sessionId) {
385
- const rows = db.select().from(messages).where(eq3(messages.sessionId, sessionId)).orderBy(messages.timestamp).all();
476
+ const rows = db.select().from(messages).where(eq3(messages.sessionId, sessionId)).orderBy(messages.sequence, messages.timestamp).all();
386
477
  return rows.map(toMessageRecord);
387
478
  }
479
+ function getMaxSequence(db, sessionId) {
480
+ const result = db.select({ maxSeq: max(messages.sequence) }).from(messages).where(eq3(messages.sessionId, sessionId)).get();
481
+ return result?.maxSeq ?? 0;
482
+ }
388
483
  function getMessage(db, id, ctx) {
389
484
  const tenantCondition = buildMessagesTenantCondition(ctx);
390
485
  const condition = tenantCondition ? and3(eq3(messages.id, id), tenantCondition) : eq3(messages.id, id);
@@ -392,7 +487,8 @@ function getMessage(db, id, ctx) {
392
487
  return row ? toMessageRecord(row) : null;
393
488
  }
394
489
  function saveMessage(db, input, ctx) {
395
- const now = /* @__PURE__ */ new Date();
490
+ const now = Date.now();
491
+ const sequence = getMaxSequence(db, input.sessionId) + 1;
396
492
  const record = {
397
493
  id: input.id,
398
494
  sessionId: input.sessionId,
@@ -400,12 +496,14 @@ function saveMessage(db, input, ctx) {
400
496
  userId: ctx.userId || null,
401
497
  role: input.role,
402
498
  content: input.content,
499
+ images: input.images || [],
403
500
  model: input.model,
404
501
  mode: input.mode,
405
502
  webSearchEnabled: input.webSearchEnabled,
406
503
  thinkingEnabled: input.thinkingEnabled,
407
504
  steps: input.steps,
408
505
  operationIds: input.operationIds,
506
+ sequence,
409
507
  timestamp: now
410
508
  };
411
509
  db.insert(messages).values({
@@ -415,15 +513,18 @@ function saveMessage(db, input, ctx) {
415
513
  userId: record.userId,
416
514
  role: record.role,
417
515
  content: record.content,
516
+ images: record.images.length > 0 ? JSON.stringify(record.images) : null,
418
517
  model: record.model,
419
518
  mode: record.mode,
420
519
  webSearchEnabled: record.webSearchEnabled,
421
520
  thinkingEnabled: record.thinkingEnabled,
422
521
  steps: record.steps,
423
522
  operationIds: record.operationIds,
424
- timestamp: record.timestamp
523
+ sequence: record.sequence,
524
+ timestamp: new Date(record.timestamp)
525
+ // SQLite schema 需要 Date 对象,但 record.timestamp 是 number
425
526
  }).run();
426
- db.update(sessions).set({ updatedAt: now }).where(eq3(sessions.id, input.sessionId)).run();
527
+ db.update(sessions).set({ updatedAt: new Date(now) }).where(eq3(sessions.id, input.sessionId)).run();
427
528
  return record;
428
529
  }
429
530
  function updateMessage(db, id, data, ctx) {
@@ -446,6 +547,15 @@ function deleteMessagesAfter(db, sessionId, timestamp) {
446
547
  gt(messages.timestamp, timestamp)
447
548
  )).run();
448
549
  }
550
+ function deleteMessagesAfterMessage(db, sessionId, anchorId, anchorSequence) {
551
+ if (anchorSequence === null || anchorSequence === void 0) {
552
+ throw new Error(`\u6D88\u606F ${anchorId} \u7F3A\u5C11\u5E8F\u53F7\uFF0C\u65E0\u6CD5\u5220\u9664\u540E\u7EED\u6D88\u606F`);
553
+ }
554
+ db.delete(messages).where(and3(
555
+ eq3(messages.sessionId, sessionId),
556
+ gt(messages.sequence, anchorSequence)
557
+ )).run();
558
+ }
449
559
  function deleteSessionMessages(db, sessionId) {
450
560
  db.delete(messages).where(eq3(messages.sessionId, sessionId)).run();
451
561
  }
@@ -761,6 +871,17 @@ var SqliteAdapter = class {
761
871
  if (!session) return;
762
872
  deleteMessagesAfter(this.db, sessionId, timestamp);
763
873
  }
874
+ async deleteMessagesAfterMessageId(sessionId, messageId, ctx) {
875
+ const session = await this.getSession(sessionId, ctx);
876
+ if (!session) return;
877
+ const anchor = await this.getMessage(messageId, ctx);
878
+ if (!anchor) return;
879
+ if (anchor.sessionId !== sessionId) return;
880
+ if (anchor.sequence === null || anchor.sequence === void 0) {
881
+ throw new Error(`\u6D88\u606F ${messageId} \u7F3A\u5C11\u5E8F\u53F7\uFF0C\u65E0\u6CD5\u5220\u9664\u540E\u7EED\u6D88\u606F`);
882
+ }
883
+ deleteMessagesAfterMessage(this.db, sessionId, messageId, anchor.sequence);
884
+ }
764
885
  // ============ 操作日志 ============
765
886
  async getOperations(sessionId, ctx) {
766
887
  const session = await this.getSession(sessionId, ctx);
@@ -28,15 +28,51 @@ async function initSchema(sql) {
28
28
  user_id TEXT,
29
29
  role TEXT NOT NULL,
30
30
  content TEXT NOT NULL,
31
+ images TEXT,
31
32
  model TEXT,
32
33
  mode TEXT,
33
34
  web_search_enabled BOOLEAN,
34
35
  thinking_enabled BOOLEAN,
35
36
  steps TEXT,
36
37
  operation_ids TEXT,
38
+ sequence INTEGER,
37
39
  timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
38
40
  )
39
41
  `;
42
+ try {
43
+ await sql`ALTER TABLE messages ADD COLUMN IF NOT EXISTS sequence INTEGER`;
44
+ } catch {
45
+ }
46
+ try {
47
+ await sql`ALTER TABLE messages ADD COLUMN IF NOT EXISTS images TEXT`;
48
+ } catch {
49
+ }
50
+ try {
51
+ await sql`
52
+ UPDATE messages
53
+ SET sequence = sub.rank
54
+ FROM (
55
+ SELECT id, ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY timestamp, id) as rank
56
+ FROM messages
57
+ WHERE sequence IS NULL
58
+ ) sub
59
+ WHERE messages.id = sub.id
60
+ `;
61
+ await sql`
62
+ UPDATE messages
63
+ SET sequence = (
64
+ SELECT COALESCE(MAX(sequence), 0) + 1
65
+ FROM messages m2
66
+ WHERE m2.session_id = messages.session_id
67
+ )
68
+ WHERE sequence IS NULL
69
+ `;
70
+ } catch {
71
+ }
72
+ try {
73
+ await sql`CREATE INDEX IF NOT EXISTS idx_messages_sequence ON messages(session_id, sequence)`;
74
+ } catch {
75
+ }
40
76
  await sql`
41
77
  CREATE TABLE IF NOT EXISTS operations (
42
78
  id TEXT PRIMARY KEY,
@@ -241,6 +277,39 @@ async function deleteSession(sql, id, ctx) {
241
277
 
242
278
  // src/adapters/postgres/messages.ts
243
279
  function toMessageRecord(row) {
280
+ if (row.sequence === null || row.sequence === void 0) {
281
+ throw new Error(`\u6D88\u606F ${row.id} \u7F3A\u5C11\u5E8F\u53F7\uFF0C\u6570\u636E\u635F\u574F`);
282
+ }
283
+ const toSequence = (seq) => {
284
+ if (typeof seq === "number" && Number.isFinite(seq)) return seq;
285
+ if (typeof seq === "string") {
286
+ const parsed = Number(seq);
287
+ if (Number.isFinite(parsed)) return parsed;
288
+ }
289
+ throw new Error(`\u6D88\u606F ${row.id} \u7684\u5E8F\u53F7\u65E0\u6548: ${seq}`);
290
+ };
291
+ const toTimestamp = (ts) => {
292
+ if (typeof ts === "number" && Number.isFinite(ts)) return ts;
293
+ if (ts instanceof Date) return ts.getTime();
294
+ if (typeof ts === "string") {
295
+ const parsed = Date.parse(ts);
296
+ if (!Number.isNaN(parsed)) return parsed;
297
+ const asNum = Number(ts);
298
+ if (Number.isFinite(asNum)) return asNum;
299
+ }
300
+ throw new Error(`\u6D88\u606F ${row.id} \u7684\u65F6\u95F4\u6233\u65E0\u6548: ${ts}`);
301
+ };
302
+ let images = [];
303
+ if (row.images) {
304
+ try {
305
+ const parsed = JSON.parse(row.images);
306
+ if (Array.isArray(parsed)) {
307
+ images = parsed;
308
+ }
309
+ } catch {
310
+ console.warn(`\u6D88\u606F ${row.id} \u7684 images \u5B57\u6BB5\u89E3\u6790\u5931\u8D25:`, row.images);
311
+ }
312
+ }
244
313
  return {
245
314
  id: row.id,
246
315
  sessionId: row.session_id,
@@ -248,29 +317,41 @@ function toMessageRecord(row) {
248
317
  userId: row.user_id,
249
318
  role: row.role,
250
319
  content: row.content,
320
+ images,
251
321
  model: row.model,
252
322
  mode: row.mode,
323
+ // PostgreSQL 的 BOOLEAN 类型直接返回 boolean | null
253
324
  webSearchEnabled: row.web_search_enabled,
254
325
  thinkingEnabled: row.thinking_enabled,
255
326
  steps: row.steps,
256
327
  operationIds: row.operation_ids,
257
- timestamp: new Date(row.timestamp)
328
+ sequence: toSequence(row.sequence),
329
+ timestamp: toTimestamp(row.timestamp)
258
330
  };
259
331
  }
260
332
  async function getMessages(sql, sessionId) {
261
333
  const rows = await sql`
262
- SELECT id, session_id, app_id, user_id, role, content, model, mode,
263
- web_search_enabled, thinking_enabled, steps, operation_ids, timestamp
334
+ SELECT id, session_id, app_id, user_id, role, content, images, model, mode,
335
+ web_search_enabled, thinking_enabled, steps, operation_ids, sequence, timestamp
264
336
  FROM messages
265
337
  WHERE session_id = ${sessionId}
266
- ORDER BY timestamp
338
+ ORDER BY sequence NULLS LAST, timestamp
267
339
  `;
268
340
  return rows.map(toMessageRecord);
269
341
  }
342
+ async function getMaxSequence(sql, sessionId) {
343
+ const rows = await sql`
344
+ SELECT MAX(sequence) as max_seq
345
+ FROM messages
346
+ WHERE session_id = ${sessionId}
347
+ `;
348
+ const maxSeq = rows[0]?.max_seq;
349
+ return maxSeq !== null && maxSeq !== void 0 ? maxSeq : 0;
350
+ }
270
351
  async function getMessage(sql, id, ctx) {
271
352
  const rows = await sql`
272
- SELECT id, session_id, app_id, user_id, role, content, model, mode,
273
- web_search_enabled, thinking_enabled, steps, operation_ids, timestamp
353
+ SELECT id, session_id, app_id, user_id, role, content, images, model, mode,
354
+ web_search_enabled, thinking_enabled, steps, operation_ids, sequence, timestamp
274
355
  FROM messages
275
356
  WHERE id = ${id}
276
357
  AND (${ctx.appId || null}::TEXT IS NULL OR app_id = ${ctx.appId || null})
@@ -279,12 +360,16 @@ async function getMessage(sql, id, ctx) {
279
360
  return rows[0] ? toMessageRecord(rows[0]) : null;
280
361
  }
281
362
  async function saveMessage(sql, input, ctx) {
282
- const now = /* @__PURE__ */ new Date();
363
+ const now = Date.now();
364
+ const nowDate = new Date(now);
365
+ const sequence = await getMaxSequence(sql, input.sessionId);
366
+ const nextSequence = sequence + 1;
367
+ const imagesJson = input.images && input.images.length > 0 ? JSON.stringify(input.images) : null;
283
368
  await sql`
284
- INSERT INTO messages (id, session_id, app_id, user_id, role, content, model, mode, web_search_enabled, thinking_enabled, steps, operation_ids, timestamp)
285
- VALUES (${input.id}, ${input.sessionId}, ${ctx.appId || null}, ${ctx.userId || null}, ${input.role}, ${input.content}, ${input.model}, ${input.mode}, ${input.webSearchEnabled}, ${input.thinkingEnabled}, ${input.steps}, ${input.operationIds}, ${now})
369
+ INSERT INTO messages (id, session_id, app_id, user_id, role, content, images, model, mode, web_search_enabled, thinking_enabled, steps, operation_ids, sequence, timestamp)
370
+ VALUES (${input.id}, ${input.sessionId}, ${ctx.appId || null}, ${ctx.userId || null}, ${input.role}, ${input.content}, ${imagesJson}, ${input.model}, ${input.mode}, ${input.webSearchEnabled}, ${input.thinkingEnabled}, ${input.steps}, ${input.operationIds}, ${nextSequence}, ${nowDate})
286
371
  `;
287
- await sql`UPDATE sessions SET updated_at = ${now} WHERE id = ${input.sessionId}`;
372
+ await sql`UPDATE sessions SET updated_at = ${nowDate} WHERE id = ${input.sessionId}`;
288
373
  return {
289
374
  id: input.id,
290
375
  sessionId: input.sessionId,
@@ -292,13 +377,16 @@ async function saveMessage(sql, input, ctx) {
292
377
  userId: ctx.userId || null,
293
378
  role: input.role,
294
379
  content: input.content,
380
+ images: input.images || [],
295
381
  model: input.model,
296
382
  mode: input.mode,
297
383
  webSearchEnabled: input.webSearchEnabled,
298
384
  thinkingEnabled: input.thinkingEnabled,
299
385
  steps: input.steps,
300
386
  operationIds: input.operationIds,
387
+ sequence: nextSequence,
301
388
  timestamp: now
389
+ // 返回 number,不受 JSON 序列化影响
302
390
  };
303
391
  }
304
392
  async function updateMessage(sql, id, data, ctx) {
@@ -343,6 +431,16 @@ async function deleteMessagesAfter(sql, sessionId, timestamp) {
343
431
  AND timestamp > ${timestamp}
344
432
  `;
345
433
  }
434
+ async function deleteMessagesAfterMessage(sql, sessionId, anchorId, anchorSequence) {
435
+ if (anchorSequence === null || anchorSequence === void 0) {
436
+ throw new Error(`\u6D88\u606F ${anchorId} \u7F3A\u5C11\u5E8F\u53F7\uFF0C\u65E0\u6CD5\u5220\u9664\u540E\u7EED\u6D88\u606F`);
437
+ }
438
+ await sql`
439
+ DELETE FROM messages
440
+ WHERE session_id = ${sessionId}
441
+ AND sequence > ${anchorSequence}
442
+ `;
443
+ }
346
444
  async function deleteSessionMessages(sql, sessionId) {
347
445
  await sql`DELETE FROM messages WHERE session_id = ${sessionId}`;
348
446
  }
@@ -646,6 +744,18 @@ var PostgresAdapter = class {
646
744
  if (!session) return;
647
745
  await deleteMessagesAfter(this.sql, sessionId, timestamp);
648
746
  }
747
+ async deleteMessagesAfterMessageId(sessionId, messageId, ctx) {
748
+ await this.ensureInitialized();
749
+ const session = await this.getSession(sessionId, ctx);
750
+ if (!session) return;
751
+ const anchor = await this.getMessage(messageId, ctx);
752
+ if (!anchor) return;
753
+ if (anchor.sessionId !== sessionId) return;
754
+ if (anchor.sequence === null || anchor.sequence === void 0) {
755
+ throw new Error(`\u6D88\u606F ${messageId} \u7F3A\u5C11\u5E8F\u53F7\uFF0C\u65E0\u6CD5\u5220\u9664\u540E\u7EED\u6D88\u606F`);
756
+ }
757
+ await deleteMessagesAfterMessage(this.sql, sessionId, messageId, anchor.sequence);
758
+ }
649
759
  // ============ 操作日志 ============
650
760
  async getOperations(sessionId, ctx) {
651
761
  await this.ensureInitialized();
package/dist/index.d.ts CHANGED
@@ -33,6 +33,8 @@ interface MessageRecord {
33
33
  userId: string | null;
34
34
  role: 'user' | 'assistant';
35
35
  content: string;
36
+ /** 用户消息附带的图片(data URL 或 base64) */
37
+ images: string[];
36
38
  /** 生成此消息时使用的模型 */
37
39
  model: string | null;
38
40
  /** 生成此消息时使用的模式 (ask/agent) */
@@ -44,9 +46,12 @@ interface MessageRecord {
44
46
  /** 执行步骤列表 JSON */
45
47
  steps: string | null;
46
48
  operationIds: string | null;
47
- timestamp: Date;
49
+ /** 消息序号(会话内递增,用于分叉删除,必须) */
50
+ sequence: number;
51
+ /** 时间戳(毫秒,Unix 时间戳,不受 JSON 序列化影响) */
52
+ timestamp: number;
48
53
  }
49
- type CreateMessageInput = Omit<MessageRecord, 'timestamp' | 'appId' | 'userId'>;
54
+ type CreateMessageInput = Omit<MessageRecord, 'timestamp' | 'appId' | 'userId' | 'sequence'>;
50
55
  /** 更新消息输入(只更新 content 和 steps) */
51
56
  type UpdateMessageInput = Partial<Pick<MessageRecord, 'content' | 'steps'>>;
52
57
  type OperationStatus = 'pending' | 'confirmed' | 'executed' | 'reverted' | 'failed';
@@ -123,6 +128,14 @@ interface StorageAdapter {
123
128
  /** 更新消息(用于增量保存助手消息) */
124
129
  updateMessage(id: string, data: UpdateMessageInput, ctx: StorageContext): Promise<void>;
125
130
  deleteMessagesAfter(sessionId: string, timestamp: Date, ctx: StorageContext): Promise<void>;
131
+ /**
132
+ * 删除指定消息之后的所有消息(用于分叉/重新发送)
133
+ *
134
+ * 说明:
135
+ * - 前端/IPC 里 timestamp 可能出现 Date/string 等序列化差异
136
+ * - 用 messageId 作为锚点更稳定:后端根据 messageId 查出 timestamp,再执行 deleteMessagesAfter
137
+ */
138
+ deleteMessagesAfterMessageId(sessionId: string, messageId: string, ctx: StorageContext): Promise<void>;
126
139
  getOperations(sessionId: string, ctx: StorageContext): Promise<OperationRecord[]>;
127
140
  getOperationsByMessage(messageId: string, ctx: StorageContext): Promise<OperationRecord[]>;
128
141
  saveOperation(operation: CreateOperationInput, ctx: StorageContext): Promise<OperationRecord>;
@@ -484,6 +497,25 @@ declare const messages: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
484
497
  }, {}, {
485
498
  length: number | undefined;
486
499
  }>;
500
+ images: drizzle_orm_sqlite_core.SQLiteColumn<{
501
+ name: "images";
502
+ tableName: "messages";
503
+ dataType: "string";
504
+ columnType: "SQLiteText";
505
+ data: string;
506
+ driverParam: string;
507
+ notNull: false;
508
+ hasDefault: false;
509
+ isPrimaryKey: false;
510
+ isAutoincrement: false;
511
+ hasRuntimeDefault: false;
512
+ enumValues: [string, ...string[]];
513
+ baseColumn: never;
514
+ identity: undefined;
515
+ generated: undefined;
516
+ }, {}, {
517
+ length: number | undefined;
518
+ }>;
487
519
  model: drizzle_orm_sqlite_core.SQLiteColumn<{
488
520
  name: "model";
489
521
  tableName: "messages";
@@ -594,6 +626,23 @@ declare const messages: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
594
626
  }, {}, {
595
627
  length: number | undefined;
596
628
  }>;
629
+ sequence: drizzle_orm_sqlite_core.SQLiteColumn<{
630
+ name: "sequence";
631
+ tableName: "messages";
632
+ dataType: "number";
633
+ columnType: "SQLiteInteger";
634
+ data: number;
635
+ driverParam: number;
636
+ notNull: true;
637
+ hasDefault: false;
638
+ isPrimaryKey: false;
639
+ isAutoincrement: false;
640
+ hasRuntimeDefault: false;
641
+ enumValues: undefined;
642
+ baseColumn: never;
643
+ identity: undefined;
644
+ generated: undefined;
645
+ }, {}, {}>;
597
646
  timestamp: drizzle_orm_sqlite_core.SQLiteColumn<{
598
647
  name: "timestamp";
599
648
  tableName: "messages";
@@ -1376,6 +1425,7 @@ declare class SqliteAdapter implements StorageAdapter {
1376
1425
  saveMessage(input: CreateMessageInput, ctx: StorageContext): Promise<MessageRecord>;
1377
1426
  updateMessage(id: string, data: UpdateMessageInput, ctx: StorageContext): Promise<void>;
1378
1427
  deleteMessagesAfter(sessionId: string, timestamp: Date, ctx: StorageContext): Promise<void>;
1428
+ deleteMessagesAfterMessageId(sessionId: string, messageId: string, ctx: StorageContext): Promise<void>;
1379
1429
  getOperations(sessionId: string, ctx: StorageContext): Promise<OperationRecord[]>;
1380
1430
  getOperationsByMessage(messageId: string, ctx: StorageContext): Promise<OperationRecord[]>;
1381
1431
  saveOperation(input: CreateOperationInput, ctx: StorageContext): Promise<OperationRecord>;
@@ -1422,6 +1472,7 @@ declare class PostgresAdapter implements StorageAdapter {
1422
1472
  saveMessage(input: CreateMessageInput, ctx: StorageContext): Promise<MessageRecord>;
1423
1473
  updateMessage(id: string, data: UpdateMessageInput, ctx: StorageContext): Promise<void>;
1424
1474
  deleteMessagesAfter(sessionId: string, timestamp: Date, ctx: StorageContext): Promise<void>;
1475
+ deleteMessagesAfterMessageId(sessionId: string, messageId: string, ctx: StorageContext): Promise<void>;
1425
1476
  getOperations(sessionId: string, ctx: StorageContext): Promise<OperationRecord[]>;
1426
1477
  getOperationsByMessage(messageId: string, ctx: StorageContext): Promise<OperationRecord[]>;
1427
1478
  saveOperation(input: CreateOperationInput, ctx: StorageContext): Promise<OperationRecord>;
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ import {
6
6
  operations,
7
7
  sessions,
8
8
  trash
9
- } from "./chunk-LZ2LBU6Q.js";
9
+ } from "./chunk-6L6KHCOT.js";
10
10
  import {
11
11
  PostgresAdapter
12
- } from "./chunk-7AKL7W3Y.js";
12
+ } from "./chunk-CQ66EXL6.js";
13
13
 
14
14
  // src/index.ts
15
15
  import { mkdirSync, existsSync } from "fs";
@@ -24,14 +24,14 @@ async function createStorage(config) {
24
24
  if (!existsSync(dir)) {
25
25
  mkdirSync(dir, { recursive: true });
26
26
  }
27
- const { SqliteAdapter: SqliteAdapter2 } = await import("./sqlite-5SSRI3KS.js");
27
+ const { SqliteAdapter: SqliteAdapter2 } = await import("./sqlite-MZGQZHVI.js");
28
28
  return new SqliteAdapter2(config.sqlitePath, config);
29
29
  }
30
30
  case "postgres": {
31
31
  if (!config.postgresUrl) {
32
32
  throw new Error("PostgreSQL \u9700\u8981\u63D0\u4F9B postgresUrl \u914D\u7F6E");
33
33
  }
34
- const { PostgresAdapter: PostgresAdapter2 } = await import("./postgres-4AZE26G4.js");
34
+ const { PostgresAdapter: PostgresAdapter2 } = await import("./postgres-CIPWMGYI.js");
35
35
  return new PostgresAdapter2(config.postgresUrl, config);
36
36
  }
37
37
  default:
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PostgresAdapter
3
- } from "./chunk-7AKL7W3Y.js";
3
+ } from "./chunk-CQ66EXL6.js";
4
4
  export {
5
5
  PostgresAdapter
6
6
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SqliteAdapter
3
- } from "./chunk-LZ2LBU6Q.js";
3
+ } from "./chunk-6L6KHCOT.js";
4
4
  export {
5
5
  SqliteAdapter
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huyooo/ai-chat-storage",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "description": "AI Chat 统一存储层 - SQLite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",