@deepagents/context 7.1.1 → 7.1.3

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.
Files changed (43) hide show
  1. package/dist/browser.js +7 -4
  2. package/dist/browser.js.map +2 -2
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +695 -808
  6. package/dist/index.js.map +4 -4
  7. package/dist/lib/chain-summary.d.ts +2 -6
  8. package/dist/lib/chain-summary.d.ts.map +1 -1
  9. package/dist/lib/engine.d.ts +1 -1
  10. package/dist/lib/engine.d.ts.map +1 -1
  11. package/dist/lib/estimate.d.ts.map +1 -1
  12. package/dist/lib/fragments.d.ts +8 -6
  13. package/dist/lib/fragments.d.ts.map +1 -1
  14. package/dist/lib/resolvers/index.d.ts +0 -1
  15. package/dist/lib/resolvers/index.d.ts.map +1 -1
  16. package/dist/lib/save/save-pipeline.d.ts +2 -2
  17. package/dist/lib/save/save-pipeline.d.ts.map +1 -1
  18. package/dist/lib/store/ddl.sqlserver.d.ts.map +1 -1
  19. package/dist/lib/store/postgres-schema-lock.d.ts +12 -0
  20. package/dist/lib/store/postgres-schema-lock.d.ts.map +1 -0
  21. package/dist/lib/store/postgres.store.d.ts +3 -2
  22. package/dist/lib/store/postgres.store.d.ts.map +1 -1
  23. package/dist/lib/store/sqlite.store.d.ts +9 -0
  24. package/dist/lib/store/sqlite.store.d.ts.map +1 -1
  25. package/dist/lib/store/sqlserver-schema-lock.d.ts +14 -0
  26. package/dist/lib/store/sqlserver-schema-lock.d.ts.map +1 -0
  27. package/dist/lib/store/sqlserver.store.d.ts +3 -2
  28. package/dist/lib/store/sqlserver.store.d.ts.map +1 -1
  29. package/dist/lib/stream/postgres-notify-change-source.d.ts +3 -2
  30. package/dist/lib/stream/postgres-notify-change-source.d.ts.map +1 -1
  31. package/dist/lib/stream/postgres.stream-store.d.ts +3 -2
  32. package/dist/lib/stream/postgres.stream-store.d.ts.map +1 -1
  33. package/dist/lib/stream/sqlite.stream-store.d.ts.map +1 -1
  34. package/dist/lib/tracing/batch-processor.d.ts.map +1 -1
  35. package/dist/lib/tracing/index.js +49 -29
  36. package/dist/lib/tracing/index.js.map +2 -2
  37. package/package.json +17 -9
  38. package/dist/lib/resolvers/promise-resolver.d.ts +0 -7
  39. package/dist/lib/resolvers/promise-resolver.d.ts.map +0 -1
  40. package/dist/scripts/scripts/generate-models-type.d.ts +0 -2
  41. package/dist/scripts/scripts/generate-models-type.d.ts.map +0 -1
  42. package/dist/scripts/scripts/probe-tool-output-reminders.d.ts +0 -2
  43. package/dist/scripts/scripts/probe-tool-output-reminders.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -175,7 +175,7 @@ function fitsFragmentData(value, visited) {
175
175
  if (Array.isArray(value)) {
176
176
  return value.every((item) => fitsFragmentData(item, visited));
177
177
  }
178
- if (value instanceof Promise) return true;
178
+ if (value instanceof Promise) return false;
179
179
  if (Symbol.asyncIterator in value || Symbol.iterator in value) return true;
180
180
  if (isFragment(value)) return fitsFragmentData(value.data, visited);
181
181
  return Object.values(value).every((item) => fitsFragmentData(item, visited));
@@ -2647,6 +2647,7 @@ import {
2647
2647
  isStaticToolUIPart as isStaticToolUIPart2,
2648
2648
  validateUIMessages
2649
2649
  } from "ai";
2650
+ import pMemoize2 from "p-memoize";
2650
2651
 
2651
2652
  // packages/context/src/lib/ui-message-guards.ts
2652
2653
  function isRecord2(value) {
@@ -2679,75 +2680,63 @@ function mergeReply(segments) {
2679
2680
  parts: segments.flatMap((segment) => segment.parts)
2680
2681
  };
2681
2682
  }
2682
- var ChainSummaryBuilder = class {
2683
- #turn = 0;
2684
- #messageCount = 0;
2685
- #lastMessageAt;
2686
- #lastMessage;
2687
- #lastAssistantMessage;
2688
- #lastAssistantMessages = [];
2689
- #replies = [];
2690
- #replyClosed = true;
2691
- #firedOnceIds = /* @__PURE__ */ new Set();
2692
- ingestStored(msg) {
2693
- this.#messageCount++;
2683
+ function summarizeChain(stored, pending) {
2684
+ let turn = 0;
2685
+ let lastMessageAt;
2686
+ let lastMessage;
2687
+ const assistantMessages = [];
2688
+ let reply = [];
2689
+ const replies = [reply];
2690
+ const firedOnceIds = /* @__PURE__ */ new Set();
2691
+ for (const msg of stored) {
2694
2692
  if (msg.name === "assistant") {
2695
2693
  const message3 = requireUIMessage(
2696
2694
  msg.data,
2697
2695
  `Stored assistant message "${msg.id}"`
2698
2696
  );
2699
- this.#lastAssistantMessage = message3;
2700
- this.#lastAssistantMessages.push(message3);
2701
- const open = this.#replies.at(-1);
2702
- if (open && !this.#replyClosed) open.push(message3);
2703
- else {
2704
- this.#replies.push([message3]);
2705
- this.#replyClosed = false;
2706
- }
2707
- return;
2708
- }
2709
- if (msg.name !== "user") {
2710
- return;
2697
+ assistantMessages.push(message3);
2698
+ reply.push(message3);
2699
+ continue;
2711
2700
  }
2701
+ if (msg.name !== "user") continue;
2712
2702
  const message2 = requireUIMessage(
2713
2703
  msg.data,
2714
2704
  `Stored user message "${msg.id}"`
2715
2705
  );
2716
2706
  if (isSyntheticReminderMessage(message2)) {
2717
2707
  for (const id of message2.metadata.synthetic.onceIds ?? []) {
2718
- this.#firedOnceIds.add(id);
2708
+ firedOnceIds.add(id);
2719
2709
  }
2720
- return;
2710
+ continue;
2721
2711
  }
2722
- for (const id of getReminderOnceIds(message2)) this.#firedOnceIds.add(id);
2723
- this.#turn++;
2724
- this.#lastMessageAt = msg.createdAt;
2725
- this.#lastMessage = message2;
2726
- this.#replyClosed = true;
2712
+ for (const id of getReminderOnceIds(message2)) firedOnceIds.add(id);
2713
+ turn++;
2714
+ lastMessageAt = msg.createdAt;
2715
+ lastMessage = message2;
2716
+ reply = [];
2717
+ replies.push(reply);
2727
2718
  }
2728
- ingestPending(fragment2) {
2729
- this.#messageCount++;
2730
- if (fragment2.name !== "user") return;
2719
+ for (const fragment2 of pending) {
2720
+ if (fragment2.name !== "user") continue;
2731
2721
  const encoded = fragment2.codec?.encode();
2732
- if (isUIMessage(encoded) && isSyntheticReminderMessage(encoded)) return;
2733
- this.#turn++;
2722
+ if (isUIMessage(encoded) && isSyntheticReminderMessage(encoded)) continue;
2723
+ turn++;
2734
2724
  }
2735
- build() {
2736
- return {
2737
- turn: this.#turn,
2738
- messageCount: this.#messageCount,
2739
- lastMessageAt: this.#lastMessageAt,
2740
- lastMessage: this.#lastMessage,
2741
- lastAssistantMessage: this.#lastAssistantMessage,
2742
- lastAssistantMessages: this.#lastAssistantMessages,
2743
- lastAssistantReplies: this.#replies.map(mergeReply),
2744
- firedOnceIds: this.#firedOnceIds
2745
- };
2746
- }
2747
- };
2725
+ return {
2726
+ turn,
2727
+ messageCount: stored.length + pending.length,
2728
+ lastMessageAt,
2729
+ lastMessage,
2730
+ lastAssistantMessage: assistantMessages.at(-1),
2731
+ lastAssistantMessages: assistantMessages,
2732
+ lastAssistantReplies: replies.filter((segments) => segments.length > 0).map(mergeReply),
2733
+ firedOnceIds
2734
+ };
2735
+ }
2748
2736
 
2749
2737
  // packages/context/src/lib/estimate.ts
2750
2738
  import { encode } from "gpt-tokenizer";
2739
+ import pMemoize from "p-memoize";
2751
2740
  import { z as z2 } from "zod";
2752
2741
  var defaultTokenizer = {
2753
2742
  encode(text) {
@@ -2789,14 +2778,17 @@ function inputPricePerMillion(model) {
2789
2778
  }
2790
2779
  var ModelsRegistry = class {
2791
2780
  #cache = /* @__PURE__ */ new Map();
2792
- #loaded = false;
2793
2781
  #tokenizers = /* @__PURE__ */ new Map();
2794
2782
  #defaultTokenizer = defaultTokenizer;
2783
+ /** Fetches once; concurrent calls share the fetch, and a failed one runs again. */
2784
+ #load = pMemoize(() => this.#fetchModels());
2795
2785
  /**
2796
2786
  * Load models data from models.dev API
2797
2787
  */
2798
2788
  async load() {
2799
- if (this.#loaded) return;
2789
+ await this.#load();
2790
+ }
2791
+ async #fetchModels() {
2800
2792
  const response = await fetch("https://models.dev/api.json");
2801
2793
  if (!response.ok) {
2802
2794
  throw new Error(`Failed to fetch models: ${response.statusText}`);
@@ -2815,7 +2807,6 @@ var ModelsRegistry = class {
2815
2807
  this.#cache.set(`${providerId}:${modelId}`, info);
2816
2808
  }
2817
2809
  }
2818
- this.#loaded = true;
2819
2810
  }
2820
2811
  /**
2821
2812
  * Get model info by ID
@@ -3059,20 +3050,6 @@ function pushLimited2(collected, chunk, maxItems, resolverName) {
3059
3050
  collected.push(chunk);
3060
3051
  }
3061
3052
 
3062
- // packages/context/src/lib/resolvers/promise-resolver.ts
3063
- var PromiseResolver = class {
3064
- name = "PromiseResolver";
3065
- canResolve(value) {
3066
- return value instanceof Promise;
3067
- }
3068
- async resolve(value, _ctx) {
3069
- if (!this.canResolve(value)) {
3070
- throw new TypeError(`${this.name} cannot resolve a ${typeof value}`);
3071
- }
3072
- return value;
3073
- }
3074
- };
3075
-
3076
3053
  // packages/context/src/lib/resolvers/loader-resolver.ts
3077
3054
  var FragmentLoaderResolver = class {
3078
3055
  #resolvers;
@@ -3207,7 +3184,6 @@ function defaultResolvers() {
3207
3184
  new AsyncResolver(),
3208
3185
  new GeneratorResolver(),
3209
3186
  new FunctionResolver(),
3210
- new PromiseResolver(),
3211
3187
  new IterableResolver()
3212
3188
  ];
3213
3189
  }
@@ -3224,14 +3200,14 @@ var HeadConflictError = class extends Error {
3224
3200
  var SavePipeline = class _SavePipeline {
3225
3201
  #engine;
3226
3202
  #pending;
3227
- #shouldBranch = true;
3228
- constructor(engine, pending) {
3203
+ #shouldBranch;
3204
+ constructor(engine, pending, shouldBranch) {
3229
3205
  this.#engine = engine;
3230
3206
  this.#pending = pending;
3231
- }
3232
- async applyUpdateBranching(shouldBranch) {
3233
3207
  this.#shouldBranch = shouldBranch;
3234
- if (!shouldBranch) return this;
3208
+ }
3209
+ async applyUpdateBranching() {
3210
+ if (!this.#shouldBranch) return this;
3235
3211
  for (const fragment2 of this.#pending) {
3236
3212
  if (!fragment2.id) continue;
3237
3213
  const existing = await this.#engine.store.getMessage(fragment2.id);
@@ -3323,7 +3299,10 @@ var SavePipeline = class _SavePipeline {
3323
3299
  };
3324
3300
 
3325
3301
  // packages/context/src/lib/store/sqlite.store.ts
3326
- import { DatabaseSync } from "node:sqlite";
3302
+ import { transaction } from "@zukhruf/sqlite-transaction";
3303
+ import {
3304
+ DatabaseSync
3305
+ } from "node:sqlite";
3327
3306
  import { z as z4 } from "zod";
3328
3307
 
3329
3308
  // packages/context/src/lib/store/columns.ts
@@ -3472,92 +3451,82 @@ function toMessage(row) {
3472
3451
  createdAt: row.createdAt
3473
3452
  };
3474
3453
  }
3454
+ var SearchQuerySyntaxError = class extends Error {
3455
+ constructor(query, options) {
3456
+ super(
3457
+ `The search query ${JSON.stringify(query)} is not valid FTS5 syntax. Put a term that has characters such as - or : in double quotes: "well-known" finds the term well-known.`,
3458
+ options
3459
+ );
3460
+ this.name = "SearchQuerySyntaxError";
3461
+ }
3462
+ };
3463
+ function isFts5QueryError(error) {
3464
+ return error instanceof Error && "errcode" in error && error.errcode === 1 && /^(fts5: syntax error near |no such column: |unterminated string$|unknown special query: |expected integer, got )/.test(
3465
+ error.message
3466
+ );
3467
+ }
3475
3468
  var SqliteContextStore = class extends ContextStore {
3476
3469
  #db;
3477
- #statements = /* @__PURE__ */ new Map();
3478
- /**
3479
- * Get or create a prepared statement.
3480
- * Statements are cached for the lifetime of the store to avoid
3481
- * repeated SQL parsing and compilation overhead.
3482
- */
3483
- #stmt(sql) {
3484
- let stmt = this.#statements.get(sql);
3485
- if (!stmt) {
3486
- stmt = this.#db.prepare(sql);
3487
- this.#statements.set(sql, stmt);
3488
- }
3489
- return stmt;
3490
- }
3470
+ #sql;
3491
3471
  constructor(pathOrDb) {
3492
3472
  super();
3493
3473
  this.#db = typeof pathOrDb === "string" ? new DatabaseSync(pathOrDb) : pathOrDb;
3494
3474
  this.#db.exec(ddl_sqlite_default);
3495
- }
3496
- /**
3497
- * Execute a function within a transaction.
3498
- * Automatically commits on success or rolls back on error.
3499
- */
3500
- #useTransaction(fn, mode = "deferred") {
3501
- this.#db.exec(
3502
- mode === "immediate" ? "BEGIN IMMEDIATE" : "BEGIN TRANSACTION"
3503
- );
3504
- try {
3505
- const result = fn();
3506
- this.#db.exec("COMMIT");
3507
- return result;
3508
- } catch (error) {
3509
- try {
3510
- this.#db.exec("ROLLBACK");
3511
- } catch {
3512
- }
3513
- throw error;
3514
- }
3475
+ this.#sql = this.#db.createTagStore();
3515
3476
  }
3516
3477
  // ==========================================================================
3517
3478
  // Chat Operations
3518
3479
  // ==========================================================================
3519
3480
  async createChat(chat2) {
3520
- return this.#useTransaction(() => {
3521
- const created = storedChat.parse(
3522
- this.#db.prepare(
3523
- `INSERT INTO chats (id, userId, title, metadata)
3481
+ return transaction(
3482
+ this.#db,
3483
+ () => {
3484
+ const created = storedChat.parse(
3485
+ this.#db.prepare(
3486
+ `INSERT INTO chats (id, userId, title, metadata)
3524
3487
  VALUES (?, ?, ?, ?)
3525
3488
  RETURNING *`
3526
- ).get(
3527
- chat2.id,
3528
- chat2.userId,
3529
- chat2.title ?? null,
3530
- chat2.metadata ? JSON.stringify(chat2.metadata) : null
3531
- )
3532
- );
3533
- this.#db.prepare(
3534
- `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
3489
+ ).get(
3490
+ chat2.id,
3491
+ chat2.userId,
3492
+ chat2.title ?? null,
3493
+ chat2.metadata ? JSON.stringify(chat2.metadata) : null
3494
+ )
3495
+ );
3496
+ this.#db.prepare(
3497
+ `INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
3535
3498
  VALUES (?, ?, 'main', NULL, 1, ?)`
3536
- ).run(crypto.randomUUID(), chat2.id, Date.now());
3537
- return created;
3538
- });
3499
+ ).run(crypto.randomUUID(), chat2.id, Date.now());
3500
+ return created;
3501
+ },
3502
+ { mode: "deferred" }
3503
+ );
3539
3504
  }
3540
3505
  async upsertChat(chat2) {
3541
- return this.#useTransaction(() => {
3542
- const upserted = storedChat.parse(
3543
- this.#db.prepare(
3544
- `INSERT INTO chats (id, userId, title, metadata)
3506
+ return transaction(
3507
+ this.#db,
3508
+ () => {
3509
+ const upserted = storedChat.parse(
3510
+ this.#db.prepare(
3511
+ `INSERT INTO chats (id, userId, title, metadata)
3545
3512
  VALUES (?, ?, ?, ?)
3546
3513
  ON CONFLICT(id) DO UPDATE SET id = excluded.id
3547
3514
  RETURNING *`
3548
- ).get(
3549
- chat2.id,
3550
- chat2.userId,
3551
- chat2.title ?? null,
3552
- chat2.metadata ? JSON.stringify(chat2.metadata) : null
3553
- )
3554
- );
3555
- this.#db.prepare(
3556
- `INSERT OR IGNORE INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
3515
+ ).get(
3516
+ chat2.id,
3517
+ chat2.userId,
3518
+ chat2.title ?? null,
3519
+ chat2.metadata ? JSON.stringify(chat2.metadata) : null
3520
+ )
3521
+ );
3522
+ this.#db.prepare(
3523
+ `INSERT OR IGNORE INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
3557
3524
  VALUES (?, ?, 'main', NULL, 1, ?)`
3558
- ).run(crypto.randomUUID(), chat2.id, Date.now());
3559
- return upserted;
3560
- });
3525
+ ).run(crypto.randomUUID(), chat2.id, Date.now());
3526
+ return upserted;
3527
+ },
3528
+ { mode: "deferred" }
3529
+ );
3561
3530
  }
3562
3531
  #getChat(chatId) {
3563
3532
  return storedChat.optional().parse(this.#db.prepare("SELECT * FROM chats WHERE id = ?").get(chatId));
@@ -3566,30 +3535,36 @@ var SqliteContextStore = class extends ContextStore {
3566
3535
  return this.#getChat(chatId);
3567
3536
  }
3568
3537
  async updateChat(chatId, update) {
3569
- return this.#useTransaction(() => {
3570
- const current = this.#getChat(chatId);
3571
- if (!current) {
3572
- throw new Error(`updateChat: chat "${chatId}" not found`);
3573
- }
3574
- const updates = update(current);
3575
- if (updates === void 0) return current;
3576
- const setClauses = ["updatedAt = strftime('%s', 'now') * 1000"];
3577
- const params = [];
3578
- if (updates.title !== void 0) {
3579
- setClauses.push("title = ?");
3580
- params.push(updates.title);
3581
- }
3582
- if (updates.metadata !== void 0) {
3583
- setClauses.push("metadata = ?");
3584
- params.push(JSON.stringify(updates.metadata));
3585
- }
3586
- params.push(chatId);
3587
- return storedChat.parse(
3588
- this.#db.prepare(
3589
- `UPDATE chats SET ${setClauses.join(", ")} WHERE id = ? RETURNING *`
3590
- ).get(...params)
3591
- );
3592
- }, "immediate");
3538
+ return transaction(
3539
+ this.#db,
3540
+ () => {
3541
+ const current = this.#getChat(chatId);
3542
+ if (!current) {
3543
+ throw new Error(`updateChat: chat "${chatId}" not found`);
3544
+ }
3545
+ const updates = update(current);
3546
+ if (updates === void 0) return current;
3547
+ const setClauses = [
3548
+ "updatedAt = strftime('%s', 'now') * 1000"
3549
+ ];
3550
+ const params = [];
3551
+ if (updates.title !== void 0) {
3552
+ setClauses.push("title = ?");
3553
+ params.push(updates.title);
3554
+ }
3555
+ if (updates.metadata !== void 0) {
3556
+ setClauses.push("metadata = ?");
3557
+ params.push(JSON.stringify(updates.metadata));
3558
+ }
3559
+ params.push(chatId);
3560
+ return storedChat.parse(
3561
+ this.#db.prepare(
3562
+ `UPDATE chats SET ${setClauses.join(", ")} WHERE id = ? RETURNING *`
3563
+ ).get(...params)
3564
+ );
3565
+ },
3566
+ { mode: "immediate" }
3567
+ );
3593
3568
  }
3594
3569
  async listChats(options) {
3595
3570
  const params = [];
@@ -3636,25 +3611,29 @@ var SqliteContextStore = class extends ContextStore {
3636
3611
  );
3637
3612
  }
3638
3613
  async deleteChat(chatId, options) {
3639
- return this.#useTransaction(() => {
3640
- const messageIds = z4.array(idRow).parse(
3641
- this.#db.prepare("SELECT id FROM messages WHERE chatId = ?").all(chatId)
3642
- );
3643
- let sql = "DELETE FROM chats WHERE id = ?";
3644
- const params = [chatId];
3645
- if (options?.userId !== void 0) {
3646
- sql += " AND userId = ?";
3647
- params.push(options.userId);
3648
- }
3649
- const result = this.#db.prepare(sql).run(...params);
3650
- if (result.changes > 0 && messageIds.length > 0) {
3651
- const placeholders = messageIds.map(() => "?").join(", ");
3652
- this.#db.prepare(
3653
- `DELETE FROM messages_fts WHERE messageId IN (${placeholders})`
3654
- ).run(...messageIds.map((m) => m.id));
3655
- }
3656
- return result.changes > 0;
3657
- });
3614
+ return transaction(
3615
+ this.#db,
3616
+ () => {
3617
+ const messageIds = z4.array(idRow).parse(
3618
+ this.#db.prepare("SELECT id FROM messages WHERE chatId = ?").all(chatId)
3619
+ );
3620
+ let sql = "DELETE FROM chats WHERE id = ?";
3621
+ const params = [chatId];
3622
+ if (options?.userId !== void 0) {
3623
+ sql += " AND userId = ?";
3624
+ params.push(options.userId);
3625
+ }
3626
+ const result = this.#db.prepare(sql).run(...params);
3627
+ if (result.changes > 0 && messageIds.length > 0) {
3628
+ const placeholders = messageIds.map(() => "?").join(", ");
3629
+ this.#db.prepare(
3630
+ `DELETE FROM messages_fts WHERE messageId IN (${placeholders})`
3631
+ ).run(...messageIds.map((m) => m.id));
3632
+ }
3633
+ return result.changes > 0;
3634
+ },
3635
+ { mode: "deferred" }
3636
+ );
3658
3637
  }
3659
3638
  // ==========================================================================
3660
3639
  // Message Operations (Graph Nodes)
@@ -3663,59 +3642,48 @@ var SqliteContextStore = class extends ContextStore {
3663
3642
  if (message2.parentId === message2.id) {
3664
3643
  throw new Error(`Message ${message2.id} cannot be its own parent`);
3665
3644
  }
3666
- this.#stmt(
3667
- `INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
3668
- VALUES (?, ?, ?, ?, ?, ?, ?)
3645
+ this.#sql.run`INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
3646
+ VALUES (${message2.id}, ${message2.chatId}, ${message2.parentId}, ${message2.name}, ${message2.type ?? null}, ${JSON.stringify(message2.data)}, ${message2.createdAt})
3669
3647
  ON CONFLICT(id) DO UPDATE SET
3670
3648
  name = excluded.name,
3671
3649
  type = excluded.type,
3672
- data = excluded.data`
3673
- ).run(
3674
- message2.id,
3675
- message2.chatId,
3676
- message2.parentId,
3677
- message2.name,
3678
- message2.type ?? null,
3679
- JSON.stringify(message2.data),
3680
- message2.createdAt
3681
- );
3650
+ data = excluded.data`;
3682
3651
  const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
3683
- this.#stmt(`DELETE FROM messages_fts WHERE messageId = ?`).run(message2.id);
3684
- this.#stmt(
3685
- `INSERT INTO messages_fts(messageId, chatId, name, content)
3686
- VALUES (?, ?, ?, ?)`
3687
- ).run(message2.id, message2.chatId, message2.name, content);
3652
+ this.#sql.run`DELETE FROM messages_fts WHERE messageId = ${message2.id}`;
3653
+ this.#sql.run`INSERT INTO messages_fts(messageId, chatId, name, content)
3654
+ VALUES (${message2.id}, ${message2.chatId}, ${message2.name}, ${content})`;
3688
3655
  }
3689
3656
  async addMessage(message2) {
3690
3657
  this.#insertMessage(message2);
3691
3658
  }
3692
3659
  async addMessages(messages) {
3693
3660
  if (messages.length === 0) return;
3694
- this.#useTransaction(() => {
3695
- for (const message2 of messages) {
3696
- this.#insertMessage(message2);
3697
- }
3698
- });
3661
+ transaction(
3662
+ this.#db,
3663
+ () => {
3664
+ for (const message2 of messages) {
3665
+ this.#insertMessage(message2);
3666
+ }
3667
+ },
3668
+ { mode: "deferred" }
3669
+ );
3699
3670
  }
3700
3671
  async setMessageParent(messageId, parentId) {
3701
3672
  if (parentId === messageId) {
3702
3673
  throw new Error(`Message ${messageId} cannot be its own parent`);
3703
3674
  }
3704
- const result = this.#stmt(
3705
- "UPDATE messages SET parentId = ? WHERE id = ?"
3706
- ).run(parentId, messageId);
3675
+ const result = this.#sql.run`UPDATE messages SET parentId = ${parentId} WHERE id = ${messageId}`;
3707
3676
  if (result.changes === 0) {
3708
3677
  throw new Error(`Message ${messageId} not found`);
3709
3678
  }
3710
3679
  }
3711
3680
  async getMessage(messageId) {
3712
- return storedMessage.optional().parse(this.#stmt("SELECT * FROM messages WHERE id = ?").get(messageId));
3681
+ return storedMessage.optional().parse(this.#sql.get`SELECT * FROM messages WHERE id = ${messageId}`);
3713
3682
  }
3714
3683
  async getMessageChain(headId) {
3715
3684
  return z4.array(storedMessage).parse(
3716
- this.#stmt(
3717
- `WITH RECURSIVE chain AS (
3718
- SELECT *, 0 as depth FROM messages WHERE id = ?
3685
+ this.#sql.all`WITH RECURSIVE chain AS (
3686
+ SELECT *, 0 as depth FROM messages WHERE id = ${headId}
3719
3687
  UNION ALL
3720
3688
  SELECT m.*, c.depth + 1 FROM messages m
3721
3689
  INNER JOIN chain c ON m.id = c.parentId
@@ -3723,14 +3691,11 @@ var SqliteContextStore = class extends ContextStore {
3723
3691
  )
3724
3692
  SELECT * FROM chain
3725
3693
  ORDER BY depth DESC`
3726
- ).all(headId)
3727
3694
  );
3728
3695
  }
3729
3696
  async hasChildren(messageId) {
3730
3697
  const row = hasChildrenRow.parse(
3731
- this.#stmt(
3732
- "SELECT EXISTS(SELECT 1 FROM messages WHERE parentId = ?) as hasChildren"
3733
- ).get(messageId)
3698
+ this.#sql.get`SELECT EXISTS(SELECT 1 FROM messages WHERE parentId = ${messageId}) as hasChildren`
3734
3699
  );
3735
3700
  return row.hasChildren === 1;
3736
3701
  }
@@ -3768,9 +3733,7 @@ var SqliteContextStore = class extends ContextStore {
3768
3733
  }
3769
3734
  async getActiveBranch(chatId) {
3770
3735
  return storedBranch.optional().parse(
3771
- this.#stmt(
3772
- "SELECT * FROM branches WHERE chatId = ? AND isActive = 1"
3773
- ).get(chatId)
3736
+ this.#sql.get`SELECT * FROM branches WHERE chatId = ${chatId} AND isActive = 1`
3774
3737
  );
3775
3738
  }
3776
3739
  async setActiveBranch(chatId, branchId) {
@@ -3778,9 +3741,7 @@ var SqliteContextStore = class extends ContextStore {
3778
3741
  this.#db.prepare("UPDATE branches SET isActive = 1 WHERE id = ?").run(branchId);
3779
3742
  }
3780
3743
  async updateBranchHead(branchId, messageId, expectedHeadMessageId) {
3781
- const result = this.#stmt(
3782
- "UPDATE branches SET headMessageId = ? WHERE id = ? AND headMessageId IS ?"
3783
- ).run(messageId, branchId, expectedHeadMessageId);
3744
+ const result = this.#sql.run`UPDATE branches SET headMessageId = ${messageId} WHERE id = ${branchId} AND headMessageId IS ${expectedHeadMessageId}`;
3784
3745
  return result.changes > 0;
3785
3746
  }
3786
3747
  async listBranches(chatId) {
@@ -3876,7 +3837,15 @@ var SqliteContextStore = class extends ContextStore {
3876
3837
  }
3877
3838
  sql += " ORDER BY fts.rank LIMIT ?";
3878
3839
  params.push(limit);
3879
- return z4.array(searchResult).parse(this.#db.prepare(sql).all(...params));
3840
+ const statement2 = this.#db.prepare(sql);
3841
+ try {
3842
+ return z4.array(searchResult).parse(statement2.all(...params));
3843
+ } catch (error) {
3844
+ if (isFts5QueryError(error)) {
3845
+ throw new SearchQuerySyntaxError(query, { cause: error });
3846
+ }
3847
+ throw error;
3848
+ }
3880
3849
  }
3881
3850
  // ==========================================================================
3882
3851
  // Visualization Operations
@@ -4101,6 +4070,25 @@ function mergeTerminalToolParts(message2, liveParts) {
4101
4070
  parts.push(...remaining.values());
4102
4071
  return { ...message2, parts };
4103
4072
  }
4073
+ var UNOPENED = {
4074
+ chat: null,
4075
+ branchName: "main",
4076
+ headMessageId: void 0
4077
+ };
4078
+ var OpenChat = class {
4079
+ chat;
4080
+ branch;
4081
+ constructor(chat2, branch) {
4082
+ this.chat = chat2;
4083
+ this.branch = branch;
4084
+ }
4085
+ get branchName() {
4086
+ return this.branch.name;
4087
+ }
4088
+ get headMessageId() {
4089
+ return this.branch.headMessageId ?? void 0;
4090
+ }
4091
+ };
4104
4092
  var ContextEngine = class _ContextEngine {
4105
4093
  /** Non-message fragments (role, hints, etc.) - not persisted in graph */
4106
4094
  #fragments = [];
@@ -4109,23 +4097,13 @@ var ContextEngine = class _ContextEngine {
4109
4097
  #store;
4110
4098
  #chatId;
4111
4099
  #userId;
4112
- #branchName;
4113
- #branch = null;
4114
- #chatData = null;
4115
- #initialized = false;
4116
- /** Initial metadata to merge on first initialization */
4117
- #initialMetadata;
4100
+ /** What this engine knows of its chat: nothing until the first store call opens it. */
4101
+ #session = UNOPENED;
4102
+ /** Opens the chat once; concurrent first calls share it, and a failed open runs again. */
4103
+ #open;
4118
4104
  #loaderResolver;
4119
4105
  /** Retained so forked children inherit the parent's resolver chain */
4120
4106
  #resolvers;
4121
- get #activeBranch() {
4122
- if (!this.#branch) {
4123
- throw new Error(
4124
- "Branch not initialized. Call #ensureInitialized() first."
4125
- );
4126
- }
4127
- return this.#branch;
4128
- }
4129
4107
  get #renderableFragments() {
4130
4108
  return this.#fragments.filter((f) => !isConditionalReminder(f));
4131
4109
  }
@@ -4139,38 +4117,30 @@ var ContextEngine = class _ContextEngine {
4139
4117
  this.#store = options.store;
4140
4118
  this.#chatId = options.chatId;
4141
4119
  this.#userId = options.userId;
4142
- this.#branchName = "main";
4143
- this.#initialMetadata = options.metadata;
4120
+ this.#open = pMemoize2(() => this.#openChat(options.metadata));
4144
4121
  this.#resolvers = options.resolvers;
4145
4122
  this.#loaderResolver = new FragmentLoaderResolver(
4146
4123
  options.resolvers ?? defaultResolvers()
4147
4124
  );
4148
4125
  }
4149
4126
  /**
4150
- * Initialize the chat and branch if they don't exist.
4127
+ * Creates the chat if it is new, checks its owner, merges the initial
4128
+ * metadata, and reads the active branch.
4151
4129
  */
4152
- async #ensureInitialized() {
4153
- if (this.#initialized) {
4154
- return;
4155
- }
4156
- this.#chatData = await this.#store.upsertChat({
4130
+ async #openChat(metadata) {
4131
+ let chat2 = await this.#store.upsertChat({
4157
4132
  id: this.#chatId,
4158
4133
  userId: this.#userId
4159
4134
  });
4160
- if (this.#chatData.userId !== this.#userId) {
4135
+ if (chat2.userId !== this.#userId) {
4161
4136
  throw new Error(
4162
- `chat "${this.#chatId}" belongs to user "${this.#chatData.userId}", not "${this.#userId}"`
4137
+ `chat "${this.#chatId}" belongs to user "${chat2.userId}", not "${this.#userId}"`
4163
4138
  );
4164
4139
  }
4165
- if (this.#initialMetadata) {
4166
- const initialMetadata = this.#initialMetadata;
4167
- this.#chatData = await this.#store.updateChat(
4168
- this.#chatId,
4169
- ({ metadata }) => ({
4170
- metadata: { ...metadata, ...initialMetadata }
4171
- })
4172
- );
4173
- this.#initialMetadata = void 0;
4140
+ if (metadata) {
4141
+ chat2 = await this.#store.updateChat(this.#chatId, (stored) => ({
4142
+ metadata: { ...stored.metadata, ...metadata }
4143
+ }));
4174
4144
  }
4175
4145
  const branch = await this.#store.getActiveBranch(this.#chatId);
4176
4146
  if (!branch) {
@@ -4178,20 +4148,21 @@ var ContextEngine = class _ContextEngine {
4178
4148
  `Active branch not found for chat "${this.#chatId}" after upsertChat`
4179
4149
  );
4180
4150
  }
4181
- this.#branch = branch;
4182
- this.#branchName = branch.name;
4183
- this.#initialized = true;
4151
+ const open = new OpenChat(chat2, branch);
4152
+ this.#session = open;
4153
+ return open;
4184
4154
  }
4185
4155
  /**
4186
4156
  * Create a new branch from a specific message.
4187
4157
  * Shared logic between rewind() and btw().
4188
4158
  */
4189
- async #createBranchFrom(messageId, switchTo) {
4159
+ async #createBranchFrom(open, messageId, switchTo) {
4190
4160
  const branches = await this.#store.listBranches(this.#chatId);
4161
+ const baseName = open.branch.name;
4191
4162
  const samePrefix = branches.filter(
4192
- (it) => it.name === this.#branchName || it.name.startsWith(`${this.#branchName}-v`)
4163
+ (it) => it.name === baseName || it.name.startsWith(`${baseName}-v`)
4193
4164
  );
4194
- const newBranchName = `${this.#branchName}-v${samePrefix.length + 1}`;
4165
+ const newBranchName = `${baseName}-v${samePrefix.length + 1}`;
4195
4166
  const newBranch = {
4196
4167
  id: crypto.randomUUID(),
4197
4168
  chatId: this.#chatId,
@@ -4203,8 +4174,7 @@ var ContextEngine = class _ContextEngine {
4203
4174
  await this.#store.createBranch(newBranch);
4204
4175
  if (switchTo) {
4205
4176
  await this.#store.setActiveBranch(this.#chatId, newBranch.id);
4206
- this.#branch = { ...newBranch, isActive: true };
4207
- this.#branchName = newBranchName;
4177
+ open.branch = { ...newBranch, isActive: true };
4208
4178
  this.#pendingMessages = [];
4209
4179
  }
4210
4180
  const chain = await this.#store.getMessageChain(messageId);
@@ -4236,47 +4206,40 @@ var ContextEngine = class _ContextEngine {
4236
4206
  * Get the current branch name.
4237
4207
  */
4238
4208
  get branch() {
4239
- return this.#branchName;
4209
+ return this.#session.branchName;
4240
4210
  }
4241
4211
  /**
4242
4212
  * Get the current branch head message ID.
4243
4213
  * Returns undefined if no messages have been saved yet.
4244
4214
  */
4245
4215
  get headMessageId() {
4246
- return this.#branch?.headMessageId ?? void 0;
4216
+ return this.#session.headMessageId;
4247
4217
  }
4248
4218
  /**
4249
4219
  * Get metadata for the current chat.
4250
4220
  * Returns null if the chat hasn't been initialized yet.
4251
4221
  */
4252
4222
  get chat() {
4253
- return this.#chatData;
4223
+ return this.#session.chat;
4254
4224
  }
4255
4225
  /**
4256
4226
  * Count user turns in the conversation and return the previous saved user message context.
4257
4227
  * Includes persisted messages and pending messages in the turn count.
4258
4228
  */
4259
4229
  async #getChainContext(pending = this.#pendingMessages) {
4260
- await this.#ensureInitialized();
4261
- const builder = new ChainSummaryBuilder();
4262
- if (this.#branch?.headMessageId) {
4263
- const chain = await this.#store.getMessageChain(
4264
- this.#branch.headMessageId
4265
- );
4266
- for (const msg of chain) builder.ingestStored(msg);
4267
- }
4268
- for (const fragment2 of pending) builder.ingestPending(fragment2);
4269
- return builder.build();
4230
+ const open = await this.#open();
4231
+ const stored = open.branch.headMessageId ? await this.#store.getMessageChain(open.branch.headMessageId) : [];
4232
+ return summarizeChain(stored, pending);
4270
4233
  }
4271
4234
  async getTurnCount() {
4272
4235
  const { turn } = await this.#getChainContext();
4273
4236
  return turn;
4274
4237
  }
4275
4238
  async firstUserMessage() {
4276
- await this.#ensureInitialized();
4277
- if (this.#branch?.headMessageId) {
4239
+ const open = await this.#open();
4240
+ if (open.branch.headMessageId) {
4278
4241
  const chain = await this.#store.getMessageChain(
4279
- this.#branch.headMessageId
4242
+ open.branch.headMessageId
4280
4243
  );
4281
4244
  for (const msg of chain) {
4282
4245
  if (msg.name === "user") {
@@ -4305,7 +4268,7 @@ var ContextEngine = class _ContextEngine {
4305
4268
  * @throws if the pending tail is missing an id (programming error).
4306
4269
  */
4307
4270
  async headMessage() {
4308
- await this.#ensureInitialized();
4271
+ const open = await this.#open();
4309
4272
  if (this.#pendingMessages.length > 0) {
4310
4273
  const tail = this.#pendingMessages[this.#pendingMessages.length - 1];
4311
4274
  if (!tail.id) {
@@ -4315,8 +4278,8 @@ var ContextEngine = class _ContextEngine {
4315
4278
  }
4316
4279
  return { id: tail.id, name: tail.name };
4317
4280
  }
4318
- if (this.#branch?.headMessageId) {
4319
- const msg = await this.#store.getMessage(this.#branch.headMessageId);
4281
+ if (open.branch.headMessageId) {
4282
+ const msg = await this.#store.getMessage(open.branch.headMessageId);
4320
4283
  if (msg) return { id: msg.id, name: msg.name };
4321
4284
  }
4322
4285
  return void 0;
@@ -4336,12 +4299,12 @@ var ContextEngine = class _ContextEngine {
4336
4299
  * For id-lookup use `headMessage()` instead — that one keeps placeholders.
4337
4300
  */
4338
4301
  async getMessages() {
4339
- await this.#ensureInitialized();
4302
+ const open = await this.#open();
4340
4303
  const messages = [];
4341
4304
  const indexById = /* @__PURE__ */ new Map();
4342
- if (this.#branch?.headMessageId) {
4305
+ if (open.branch.headMessageId) {
4343
4306
  const chain = await this.#store.getMessageChain(
4344
- this.#branch.headMessageId
4307
+ open.branch.headMessageId
4345
4308
  );
4346
4309
  for (const msg of chain) {
4347
4310
  const data = requireUIMessage(msg.data, `Stored message "${msg.id}"`);
@@ -4452,7 +4415,7 @@ var ContextEngine = class _ContextEngine {
4452
4415
  * ```
4453
4416
  */
4454
4417
  async resolve(options) {
4455
- await this.#ensureInitialized();
4418
+ await this.#open();
4456
4419
  await this.#loaderResolver.resolve(this.#fragments, {
4457
4420
  sandbox: options.sandbox,
4458
4421
  context: this,
@@ -4477,15 +4440,19 @@ var ContextEngine = class _ContextEngine {
4477
4440
  * ```
4478
4441
  */
4479
4442
  async save(options) {
4480
- await this.#ensureInitialized();
4443
+ const open = await this.#open();
4481
4444
  if (this.#pendingMessages.length === 0) {
4482
- return { headMessageId: this.#branch?.headMessageId ?? void 0 };
4445
+ return { headMessageId: open.branch.headMessageId ?? void 0 };
4483
4446
  }
4484
4447
  const pending = this.#pendingMessages;
4485
4448
  this.#pendingMessages = [];
4486
- const pipeline = new SavePipeline(this.#asSavePipelineEngine(), pending);
4449
+ const pipeline = new SavePipeline(
4450
+ this.#asSavePipelineEngine(open),
4451
+ pending,
4452
+ options?.branch ?? true
4453
+ );
4487
4454
  try {
4488
- await pipeline.applyUpdateBranching(options?.branch ?? true);
4455
+ await pipeline.applyUpdateBranching();
4489
4456
  await this.#foldUserReminders(pending);
4490
4457
  return await pipeline.persist();
4491
4458
  } catch (error) {
@@ -4726,13 +4693,14 @@ var ContextEngine = class _ContextEngine {
4726
4693
  return this.#fragments.filter(isConditionalReminder).map((fragment2) => fragment2.metadata.reminder).filter((config) => config.target === target);
4727
4694
  }
4728
4695
  #buildBaseWhenCtx(chain) {
4729
- const rawUsage = this.#chatData?.metadata?.usage;
4730
- const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
4731
- const elapsed = chain.lastMessageAt !== void 0 ? Date.now() - chain.lastMessageAt : void 0;
4732
- const chatData = this.#chatData;
4733
- if (!chatData) {
4696
+ const session = this.#session;
4697
+ if (!(session instanceof OpenChat)) {
4734
4698
  throw new Error("ContextEngine must be initialized before reminders run");
4735
4699
  }
4700
+ const chatData = session.chat;
4701
+ const rawUsage = chatData.metadata?.usage;
4702
+ const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
4703
+ const elapsed = chain.lastMessageAt !== void 0 ? Date.now() - chain.lastMessageAt : void 0;
4736
4704
  return {
4737
4705
  turn: chain.turn,
4738
4706
  messageCount: chain.messageCount,
@@ -4740,7 +4708,7 @@ var ContextEngine = class _ContextEngine {
4740
4708
  lastMessage: chain.lastMessage,
4741
4709
  chat: chatData,
4742
4710
  usage,
4743
- branch: this.#branchName,
4711
+ branch: session.branchName,
4744
4712
  elapsed
4745
4713
  };
4746
4714
  }
@@ -4861,36 +4829,36 @@ var ContextEngine = class _ContextEngine {
4861
4829
  }
4862
4830
  return { texts, onceIds: [...onceIds] };
4863
4831
  }
4864
- #asSavePipelineEngine() {
4832
+ #asSavePipelineEngine(open) {
4865
4833
  return {
4866
4834
  store: this.#store,
4867
4835
  chatId: this.#chatId,
4868
4836
  getActiveBranch: () => ({
4869
- id: this.#activeBranch.id,
4870
- headMessageId: this.#activeBranch.headMessageId
4837
+ id: open.branch.id,
4838
+ headMessageId: open.branch.headMessageId
4871
4839
  }),
4872
4840
  commitHead: async (headMessageId, expectedHeadMessageId) => {
4873
4841
  const committed = await this.#store.updateBranchHead(
4874
- this.#activeBranch.id,
4842
+ open.branch.id,
4875
4843
  headMessageId,
4876
4844
  expectedHeadMessageId
4877
4845
  );
4878
4846
  if (committed) {
4879
- this.#activeBranch.headMessageId = headMessageId;
4847
+ open.branch.headMessageId = headMessageId;
4880
4848
  }
4881
4849
  return committed;
4882
4850
  },
4883
4851
  refreshBranch: async () => {
4884
4852
  const fresh = await this.#store.getBranch(
4885
4853
  this.#chatId,
4886
- this.#activeBranch.name
4854
+ open.branch.name
4887
4855
  );
4888
4856
  if (!fresh) {
4889
4857
  throw new Error(
4890
- `Branch "${this.#activeBranch.name}" not found for chat "${this.#chatId}"`
4858
+ `Branch "${open.branch.name}" not found for chat "${this.#chatId}"`
4891
4859
  );
4892
4860
  }
4893
- this.#branch = fresh;
4861
+ open.branch = fresh;
4894
4862
  return { id: fresh.id, headMessageId: fresh.headMessageId };
4895
4863
  },
4896
4864
  rewindForUpdate: (parentId) => this.#rewindForUpdate(parentId)
@@ -4909,7 +4877,7 @@ var ContextEngine = class _ContextEngine {
4909
4877
  * @returns Estimate result with token counts, costs, and per-fragment breakdown
4910
4878
  */
4911
4879
  async estimate(modelId, options = {}) {
4912
- await this.#ensureInitialized();
4880
+ const open = await this.#open();
4913
4881
  await this.#loaderResolver.resolve(this.#fragments, {
4914
4882
  sandbox: options.sandbox,
4915
4883
  context: this,
@@ -4938,9 +4906,9 @@ var ContextEngine = class _ContextEngine {
4938
4906
  cost
4939
4907
  });
4940
4908
  }
4941
- if (this.#branch?.headMessageId) {
4909
+ if (open.branch.headMessageId) {
4942
4910
  const chain = await this.#store.getMessageChain(
4943
- this.#branch.headMessageId
4911
+ open.branch.headMessageId
4944
4912
  );
4945
4913
  for (const msg of chain) {
4946
4914
  const content = estimateMessageContent(msg.data);
@@ -5012,7 +4980,7 @@ var ContextEngine = class _ContextEngine {
5012
4980
  * ```
5013
4981
  */
5014
4982
  async rewind(messageId) {
5015
- await this.#ensureInitialized();
4983
+ const open = await this.#open();
5016
4984
  const message2 = await this.#store.getMessage(messageId);
5017
4985
  if (!message2) {
5018
4986
  throw new Error(`Message "${messageId}" not found`);
@@ -5020,7 +4988,7 @@ var ContextEngine = class _ContextEngine {
5020
4988
  if (message2.chatId !== this.#chatId) {
5021
4989
  throw new Error(`Message "${messageId}" belongs to a different chat`);
5022
4990
  }
5023
- return this.#createBranchFrom(messageId, true);
4991
+ return this.#createBranchFrom(open, messageId, true);
5024
4992
  }
5025
4993
  /**
5026
4994
  * Create a checkpoint at the current position.
@@ -5042,15 +5010,15 @@ var ContextEngine = class _ContextEngine {
5042
5010
  * ```
5043
5011
  */
5044
5012
  async checkpoint(name) {
5045
- await this.#ensureInitialized();
5046
- if (!this.#branch?.headMessageId) {
5013
+ const open = await this.#open();
5014
+ if (!open.branch.headMessageId) {
5047
5015
  throw new Error("Cannot create checkpoint: no messages in conversation");
5048
5016
  }
5049
5017
  const checkpoint = {
5050
5018
  id: crypto.randomUUID(),
5051
5019
  chatId: this.#chatId,
5052
5020
  name,
5053
- messageId: this.#branch.headMessageId,
5021
+ messageId: open.branch.headMessageId,
5054
5022
  createdAt: Date.now()
5055
5023
  };
5056
5024
  await this.#store.createCheckpoint(checkpoint);
@@ -5078,7 +5046,7 @@ var ContextEngine = class _ContextEngine {
5078
5046
  * ```
5079
5047
  */
5080
5048
  async restore(name) {
5081
- await this.#ensureInitialized();
5049
+ await this.#open();
5082
5050
  const checkpoint = await this.#store.getCheckpoint(this.#chatId, name);
5083
5051
  if (!checkpoint) {
5084
5052
  throw new Error(
@@ -5103,14 +5071,13 @@ var ContextEngine = class _ContextEngine {
5103
5071
  * ```
5104
5072
  */
5105
5073
  async switchBranch(name) {
5106
- await this.#ensureInitialized();
5074
+ const open = await this.#open();
5107
5075
  const branch = await this.#store.getBranch(this.#chatId, name);
5108
5076
  if (!branch) {
5109
5077
  throw new Error(`Branch "${name}" not found in chat "${this.#chatId}"`);
5110
5078
  }
5111
5079
  await this.#store.setActiveBranch(this.#chatId, branch.id);
5112
- this.#branch = { ...branch, isActive: true };
5113
- this.#branchName = name;
5080
+ open.branch = { ...branch, isActive: true };
5114
5081
  this.#pendingMessages = [];
5115
5082
  }
5116
5083
  /**
@@ -5145,11 +5112,11 @@ var ContextEngine = class _ContextEngine {
5145
5112
  * ```
5146
5113
  */
5147
5114
  async btw() {
5148
- await this.#ensureInitialized();
5149
- if (!this.#branch?.headMessageId) {
5115
+ const open = await this.#open();
5116
+ if (!open.branch.headMessageId) {
5150
5117
  throw new Error("Cannot create btw branch: no messages in conversation");
5151
5118
  }
5152
- return this.#createBranchFrom(this.#branch.headMessageId, false);
5119
+ return this.#createBranchFrom(open, open.branch.headMessageId, false);
5153
5120
  }
5154
5121
  /**
5155
5122
  * Update metadata for the current chat.
@@ -5165,14 +5132,11 @@ var ContextEngine = class _ContextEngine {
5165
5132
  * ```
5166
5133
  */
5167
5134
  async updateChat(updates) {
5168
- await this.#ensureInitialized();
5169
- this.#chatData = await this.#store.updateChat(
5170
- this.#chatId,
5171
- ({ metadata }) => ({
5172
- title: updates.title,
5173
- metadata: updates.metadata === void 0 ? void 0 : { ...metadata, ...updates.metadata }
5174
- })
5175
- );
5135
+ const open = await this.#open();
5136
+ open.chat = await this.#store.updateChat(this.#chatId, ({ metadata }) => ({
5137
+ title: updates.title,
5138
+ metadata: updates.metadata === void 0 ? void 0 : { ...metadata, ...updates.metadata }
5139
+ }));
5176
5140
  }
5177
5141
  /**
5178
5142
  * Track token usage for the current chat.
@@ -5188,20 +5152,17 @@ var ContextEngine = class _ContextEngine {
5188
5152
  * ```
5189
5153
  */
5190
5154
  async trackUsage(usage) {
5191
- await this.#ensureInitialized();
5192
- this.#chatData = await this.#store.updateChat(
5193
- this.#chatId,
5194
- ({ metadata }) => {
5195
- const storedUsage = metadata?.usage;
5196
- const currentUsage = isLanguageModelUsage(storedUsage) ? storedUsage : void 0;
5197
- return {
5198
- metadata: {
5199
- ...metadata,
5200
- usage: mergeLanguageModelUsage(currentUsage, usage)
5201
- }
5202
- };
5203
- }
5204
- );
5155
+ const open = await this.#open();
5156
+ open.chat = await this.#store.updateChat(this.#chatId, ({ metadata }) => {
5157
+ const storedUsage = metadata?.usage;
5158
+ const currentUsage = isLanguageModelUsage(storedUsage) ? storedUsage : void 0;
5159
+ return {
5160
+ metadata: {
5161
+ ...metadata,
5162
+ usage: mergeLanguageModelUsage(currentUsage, usage)
5163
+ }
5164
+ };
5165
+ });
5205
5166
  }
5206
5167
  /**
5207
5168
  * Consolidate context fragments (no-op for now).
@@ -5267,7 +5228,7 @@ var ContextEngine = class _ContextEngine {
5267
5228
  * ```
5268
5229
  */
5269
5230
  async inspect(options) {
5270
- await this.#ensureInitialized();
5231
+ const open = await this.#open();
5271
5232
  const { renderer } = options;
5272
5233
  const estimateResult = await this.estimate(options.modelId, {
5273
5234
  renderer,
@@ -5276,9 +5237,9 @@ var ContextEngine = class _ContextEngine {
5276
5237
  });
5277
5238
  const rendered = renderer.render(this.#renderableFragments);
5278
5239
  const persistedMessages = [];
5279
- if (this.#branch?.headMessageId) {
5240
+ if (open.branch.headMessageId) {
5280
5241
  const chain = await this.#store.getMessageChain(
5281
- this.#branch.headMessageId
5242
+ open.branch.headMessageId
5282
5243
  );
5283
5244
  persistedMessages.push(...chain);
5284
5245
  }
@@ -5294,7 +5255,7 @@ var ContextEngine = class _ContextEngine {
5294
5255
  graph,
5295
5256
  meta: {
5296
5257
  chatId: this.#chatId,
5297
- branch: this.#branchName,
5258
+ branch: open.branch.name,
5298
5259
  timestamp: Date.now()
5299
5260
  }
5300
5261
  };
@@ -10740,7 +10701,7 @@ async function assertMicrosandboxBash(vm) {
10740
10701
  function normalizeMicrosandboxError(error, sdk) {
10741
10702
  const err = toError2(error);
10742
10703
  if (err instanceof MicrosandboxSandboxError) return err;
10743
- if (err instanceof sdk.LibkrunfwNotFoundError) {
10704
+ if (err instanceof sdk.LibkrunfwNotFoundError || err instanceof sdk.RuntimeNotInstalledError || err instanceof sdk.RuntimeIncompleteError) {
10744
10705
  return new MicrosandboxNotAvailableError(err);
10745
10706
  }
10746
10707
  if (err instanceof sdk.MicrosandboxError) {
@@ -11244,6 +11205,16 @@ function soul() {
11244
11205
  return fragment("soul_protocol", ...children);
11245
11206
  }
11246
11207
 
11208
+ // packages/context/src/lib/store/postgres-schema-lock.ts
11209
+ function lockSchemaDDL(ddl, schema) {
11210
+ if (schema !== void 0 && !/^[a-zA-Z_]\w*$/.test(schema)) {
11211
+ throw new Error(`Invalid schema name: "${schema}"`);
11212
+ }
11213
+ const key = schema === void 0 ? `coalesce(current_schema(), '')` : `'${schema}'`;
11214
+ return `SELECT pg_advisory_xact_lock(hashtextextended('deepagents:schema-ddl:' || ${key}, 0));
11215
+ ${ddl}`;
11216
+ }
11217
+
11247
11218
  // packages/context/src/lib/store/postgres.store.ts
11248
11219
  import { createRequire } from "node:module";
11249
11220
  import { z as z14 } from "zod";
@@ -11458,7 +11429,6 @@ var PostgresContextStore = class _PostgresContextStore extends ContextStore {
11458
11429
  #pool;
11459
11430
  #schema;
11460
11431
  #ownsPool;
11461
- #isInitialized = false;
11462
11432
  constructor(options) {
11463
11433
  super();
11464
11434
  const schema = options.schema ?? "public";
@@ -11488,24 +11458,25 @@ var PostgresContextStore = class _PostgresContextStore extends ContextStore {
11488
11458
  #t(name) {
11489
11459
  return `"${this.#schema}"."${name}"`;
11490
11460
  }
11491
- async initialize() {
11492
- const ddl = storeDDL(this.#schema);
11493
- await this.#pool.query(ddl);
11494
- this.#isInitialized = true;
11495
- }
11496
- #ensureInitialized() {
11497
- if (!this.#isInitialized) {
11498
- throw new Error(
11499
- "PostgresContextStore not initialized. Call await store.initialize() after construction."
11500
- );
11461
+ /** Opens the store: creates its schema objects before it returns. */
11462
+ static async create(options) {
11463
+ const store = new _PostgresContextStore(options);
11464
+ try {
11465
+ await store.#initialize();
11466
+ } catch (error) {
11467
+ await store.close();
11468
+ throw error;
11501
11469
  }
11470
+ return store;
11471
+ }
11472
+ async #initialize() {
11473
+ await this.#pool.query(lockSchemaDDL(storeDDL(this.#schema), this.#schema));
11502
11474
  }
11503
11475
  /**
11504
11476
  * Execute a function within a transaction.
11505
11477
  * Automatically commits on success or rolls back on error.
11506
11478
  */
11507
11479
  async #useTransaction(fn) {
11508
- this.#ensureInitialized();
11509
11480
  const client = await this.#pool.connect();
11510
11481
  try {
11511
11482
  await client.query("BEGIN");
@@ -11523,7 +11494,6 @@ var PostgresContextStore = class _PostgresContextStore extends ContextStore {
11523
11494
  * Execute a query using the pool (no transaction).
11524
11495
  */
11525
11496
  async #query(sql, params) {
11526
- this.#ensureInitialized();
11527
11497
  const result = await this.#pool.query(sql, params);
11528
11498
  return result.rows;
11529
11499
  }
@@ -12072,6 +12042,11 @@ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${schema}_branches_ch
12072
12042
  IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${schema}_branches_chatId_isActive' AND object_id = OBJECT_ID('[${schema}].[branches]'))
12073
12043
  CREATE INDEX [idx_${schema}_branches_chatId_isActive] ON [${schema}].[branches](chatId, isActive);
12074
12044
 
12045
+ -- Deleting a message checks this foreign key. Without the index the check
12046
+ -- scans every chat's branches and waits on rows another chat delete holds.
12047
+ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${schema}_branches_headMessageId' AND object_id = OBJECT_ID('[${schema}].[branches]'))
12048
+ CREATE INDEX [idx_${schema}_branches_headMessageId] ON [${schema}].[branches](headMessageId);
12049
+
12075
12050
  IF OBJECT_ID('[${schema}].[checkpoints]', 'U') IS NULL
12076
12051
  BEGIN
12077
12052
  CREATE TABLE [${schema}].[checkpoints] (
@@ -12122,6 +12097,38 @@ END;
12122
12097
  `;
12123
12098
  }
12124
12099
 
12100
+ // packages/context/src/lib/store/sqlserver-schema-lock.ts
12101
+ async function withSqlServerSchemaLock(mssql, pool, schema, run) {
12102
+ const holder = new mssql.Transaction(pool);
12103
+ await holder.begin();
12104
+ let result;
12105
+ try {
12106
+ const lock = new mssql.Request(holder);
12107
+ lock.input(
12108
+ "resource",
12109
+ mssql.NVarChar(255),
12110
+ `deepagents:schema-ddl:${schema}`
12111
+ );
12112
+ const { recordset } = await lock.query(`
12113
+ DECLARE @status int;
12114
+ EXEC @status = sp_getapplock @Resource = @resource, @LockMode = 'Exclusive', @LockOwner = 'Transaction', @LockTimeout = -1;
12115
+ SELECT @status AS status;
12116
+ `);
12117
+ if (recordset[0].status < 0) {
12118
+ throw new Error(
12119
+ `Could not lock the DDL of schema "${schema}": sp_getapplock returned ${recordset[0].status}.`
12120
+ );
12121
+ }
12122
+ result = await run();
12123
+ } catch (error) {
12124
+ await holder.rollback().catch(() => {
12125
+ });
12126
+ throw error;
12127
+ }
12128
+ await holder.rollback();
12129
+ return result;
12130
+ }
12131
+
12125
12132
  // packages/context/src/lib/store/sqlserver.store.ts
12126
12133
  var chatColumns3 = z15.object({
12127
12134
  id: z15.string(),
@@ -12225,7 +12232,6 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12225
12232
  #pool;
12226
12233
  #schema;
12227
12234
  #ownsPool;
12228
- #isInitialized = false;
12229
12235
  constructor(options) {
12230
12236
  super();
12231
12237
  const schema = options.schema ?? "dbo";
@@ -12255,50 +12261,59 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12255
12261
  #t(name) {
12256
12262
  return `[${this.#schema}].[${name}]`;
12257
12263
  }
12258
- async initialize() {
12264
+ /** Opens the store: connects a pool it owns and creates its schema objects before it returns. */
12265
+ static async create(options) {
12266
+ const store = new _SqlServerContextStore(options);
12267
+ try {
12268
+ await store.#initialize();
12269
+ } catch (error) {
12270
+ await store.close();
12271
+ throw error;
12272
+ }
12273
+ return store;
12274
+ }
12275
+ async #initialize() {
12259
12276
  if (this.#ownsPool) {
12260
12277
  await this.#pool.connect();
12261
12278
  }
12262
- const schemaReq = this.#pool.request();
12263
- schemaReq.input("schema", this.#schema);
12264
- await schemaReq.query(`
12265
- IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = @schema)
12266
- BEGIN
12267
- DECLARE @sql NVARCHAR(MAX) = N'CREATE SCHEMA ' + QUOTENAME(@schema);
12268
- EXEC sp_executesql @sql;
12269
- END
12270
- `);
12271
- const ddl = storeDDL2(this.#schema);
12272
- const batches = ddl.split(/\bGO\b/i).filter((b) => b.trim());
12273
- for (const batch of batches) {
12274
- if (batch.trim()) {
12275
- await this.#pool.request().batch(batch);
12279
+ await withSqlServerSchemaLock(
12280
+ _SqlServerContextStore.#requireMssql(),
12281
+ this.#pool,
12282
+ this.#schema,
12283
+ async () => {
12284
+ const schemaReq = this.#pool.request();
12285
+ schemaReq.input("schema", this.#schema);
12286
+ await schemaReq.query(`
12287
+ IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = @schema)
12288
+ BEGIN
12289
+ DECLARE @sql NVARCHAR(MAX) = N'CREATE SCHEMA ' + QUOTENAME(@schema);
12290
+ EXEC sp_executesql @sql;
12291
+ END
12292
+ `);
12293
+ const ddl = storeDDL2(this.#schema);
12294
+ const batches = ddl.split(/\bGO\b/i).filter((b) => b.trim());
12295
+ for (const batch of batches) {
12296
+ if (batch.trim()) {
12297
+ await this.#pool.request().batch(batch);
12298
+ }
12299
+ }
12276
12300
  }
12277
- }
12278
- this.#isInitialized = true;
12279
- }
12280
- #ensureInitialized() {
12281
- if (!this.#isInitialized) {
12282
- throw new Error(
12283
- "SqlServerContextStore not initialized. Call await store.initialize() after construction."
12284
- );
12285
- }
12301
+ );
12286
12302
  }
12287
12303
  /**
12288
12304
  * Execute a function within a transaction.
12289
12305
  * Automatically commits on success or rolls back on error.
12290
12306
  */
12291
12307
  async #useTransaction(fn) {
12292
- this.#ensureInitialized();
12293
12308
  const mssql = _SqlServerContextStore.#requireMssql();
12294
- const transaction = new mssql.Transaction(this.#pool);
12309
+ const transaction3 = new mssql.Transaction(this.#pool);
12295
12310
  try {
12296
- await transaction.begin();
12297
- const result = await fn(transaction);
12298
- await transaction.commit();
12311
+ await transaction3.begin();
12312
+ const result = await fn(transaction3);
12313
+ await transaction3.commit();
12299
12314
  return result;
12300
12315
  } catch (error) {
12301
- await transaction.rollback();
12316
+ await transaction3.rollback();
12302
12317
  throw error;
12303
12318
  }
12304
12319
  }
@@ -12307,7 +12322,6 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12307
12322
  * Converts positional params to SQL Server named params (@p0, @p1, ...).
12308
12323
  */
12309
12324
  async #query(sql, params) {
12310
- this.#ensureInitialized();
12311
12325
  const request = this.#pool.request();
12312
12326
  params?.forEach((value, index) => {
12313
12327
  request.input(`p${index}`, value);
@@ -12334,9 +12348,9 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12334
12348
  // Chat Operations
12335
12349
  // ==========================================================================
12336
12350
  async createChat(chat2) {
12337
- return this.#useTransaction(async (transaction) => {
12351
+ return this.#useTransaction(async (transaction3) => {
12338
12352
  const mssql = _SqlServerContextStore.#requireMssql();
12339
- const request = transaction.request();
12353
+ const request = transaction3.request();
12340
12354
  request.input("p0", mssql.NVarChar, chat2.id);
12341
12355
  request.input("p1", mssql.NVarChar, chat2.userId);
12342
12356
  request.input("p2", mssql.NVarChar, chat2.title ?? null);
@@ -12351,7 +12365,7 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12351
12365
  VALUES (@p0, @p1, @p2, @p3)
12352
12366
  `);
12353
12367
  const created = storedChat3.parse(result.recordset[0]);
12354
- const branchRequest = transaction.request();
12368
+ const branchRequest = transaction3.request();
12355
12369
  branchRequest.input("p0", mssql.NVarChar, crypto.randomUUID());
12356
12370
  branchRequest.input("p1", mssql.NVarChar, chat2.id);
12357
12371
  branchRequest.input("p2", mssql.BigInt, Date.now());
@@ -12363,9 +12377,9 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12363
12377
  });
12364
12378
  }
12365
12379
  async upsertChat(chat2) {
12366
- return this.#useTransaction(async (transaction) => {
12380
+ return this.#useTransaction(async (transaction3) => {
12367
12381
  const mssql = _SqlServerContextStore.#requireMssql();
12368
- const request = transaction.request();
12382
+ const request = transaction3.request();
12369
12383
  request.input("p0", mssql.NVarChar, chat2.id);
12370
12384
  request.input("p1", mssql.NVarChar, chat2.userId);
12371
12385
  request.input("p2", mssql.NVarChar, chat2.title ?? null);
@@ -12387,7 +12401,7 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12387
12401
  OUTPUT INSERTED.*;
12388
12402
  `);
12389
12403
  const upserted = storedChat3.parse(result.recordset[0]);
12390
- const branchRequest = transaction.request();
12404
+ const branchRequest = transaction3.request();
12391
12405
  branchRequest.input("p0", mssql.NVarChar, crypto.randomUUID());
12392
12406
  branchRequest.input("p1", mssql.NVarChar, chat2.id);
12393
12407
  branchRequest.input("p2", mssql.BigInt, Date.now());
@@ -12410,9 +12424,9 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12410
12424
  return chat2;
12411
12425
  }
12412
12426
  async updateChat(chatId, update) {
12413
- return this.#useTransaction(async (transaction) => {
12427
+ return this.#useTransaction(async (transaction3) => {
12414
12428
  const mssql = _SqlServerContextStore.#requireMssql();
12415
- const read = transaction.request();
12429
+ const read = transaction3.request();
12416
12430
  read.input("chatId", mssql.NVarChar, chatId);
12417
12431
  const result = await read.query(`
12418
12432
  SELECT *
@@ -12429,7 +12443,7 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12429
12443
  "updatedAt = DATEDIFF_BIG(ms, '1970-01-01', GETUTCDATE())"
12430
12444
  ];
12431
12445
  let paramIndex = 0;
12432
- const write = transaction.request();
12446
+ const write = transaction3.request();
12433
12447
  if (updates.title !== void 0) {
12434
12448
  const name = `p${paramIndex++}`;
12435
12449
  setClauses.push(`title = @${name}`);
@@ -12494,9 +12508,9 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12494
12508
  );
12495
12509
  }
12496
12510
  async deleteChat(chatId, options) {
12497
- return this.#useTransaction(async (transaction) => {
12511
+ return this.#useTransaction(async (transaction3) => {
12498
12512
  const mssql = _SqlServerContextStore.#requireMssql();
12499
- const request = transaction.request();
12513
+ const request = transaction3.request();
12500
12514
  request.input("p0", mssql.NVarChar, chatId);
12501
12515
  let sql = `DELETE FROM ${this.#t("chats")} WHERE id = @p0`;
12502
12516
  if (options?.userId !== void 0) {
@@ -12514,9 +12528,9 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12514
12528
  if (message2.parentId === message2.id) {
12515
12529
  throw new Error(`Message ${message2.id} cannot be its own parent`);
12516
12530
  }
12517
- await this.#useTransaction(async (transaction) => {
12531
+ await this.#useTransaction(async (transaction3) => {
12518
12532
  const mssql = _SqlServerContextStore.#requireMssql();
12519
- const request = transaction.request();
12533
+ const request = transaction3.request();
12520
12534
  request.input("p0", mssql.NVarChar, message2.id);
12521
12535
  request.input("p1", mssql.NVarChar, message2.chatId);
12522
12536
  request.input("p2", mssql.NVarChar, message2.parentId);
@@ -12535,7 +12549,7 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12535
12549
  VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6);
12536
12550
  `);
12537
12551
  const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
12538
- const ftsRequest = transaction.request();
12552
+ const ftsRequest = transaction3.request();
12539
12553
  ftsRequest.input("p0", mssql.NVarChar, message2.id);
12540
12554
  ftsRequest.input("p1", mssql.NVarChar, message2.chatId);
12541
12555
  ftsRequest.input("p2", mssql.NVarChar, message2.name);
@@ -12643,14 +12657,14 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12643
12657
  return branch;
12644
12658
  }
12645
12659
  async setActiveBranch(chatId, branchId) {
12646
- await this.#useTransaction(async (transaction) => {
12660
+ await this.#useTransaction(async (transaction3) => {
12647
12661
  const mssql = _SqlServerContextStore.#requireMssql();
12648
- const deactivateRequest = transaction.request();
12662
+ const deactivateRequest = transaction3.request();
12649
12663
  deactivateRequest.input("p0", mssql.NVarChar, chatId);
12650
12664
  await deactivateRequest.query(
12651
12665
  `UPDATE ${this.#t("branches")} SET isActive = 0 WHERE chatId = @p0`
12652
12666
  );
12653
- const activateRequest = transaction.request();
12667
+ const activateRequest = transaction3.request();
12654
12668
  activateRequest.input("p0", mssql.NVarChar, branchId);
12655
12669
  await activateRequest.query(
12656
12670
  `UPDATE ${this.#t("branches")} SET isActive = 1 WHERE id = @p0`
@@ -12713,9 +12727,9 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12713
12727
  // Checkpoint Operations
12714
12728
  // ==========================================================================
12715
12729
  async createCheckpoint(checkpoint) {
12716
- await this.#useTransaction(async (transaction) => {
12730
+ await this.#useTransaction(async (transaction3) => {
12717
12731
  const mssql = _SqlServerContextStore.#requireMssql();
12718
- const request = transaction.request();
12732
+ const request = transaction3.request();
12719
12733
  request.input("p0", mssql.NVarChar, checkpoint.id);
12720
12734
  request.input("p1", mssql.NVarChar, checkpoint.chatId);
12721
12735
  request.input("p2", mssql.NVarChar, checkpoint.name);
@@ -13159,7 +13173,9 @@ function staticPartText(part, options) {
13159
13173
  }
13160
13174
 
13161
13175
  // packages/context/src/lib/stream/postgres-notify-change-source.ts
13176
+ import { EventEmitter, on } from "node:events";
13162
13177
  import { createRequire as createRequire3 } from "node:module";
13178
+ import pMemoize3 from "p-memoize";
13163
13179
  import { z as z16 } from "zod";
13164
13180
 
13165
13181
  // packages/context/src/lib/stream/ddl.stream.postgres-notify.ts
@@ -13220,28 +13236,73 @@ EXECUTE FUNCTION "${schema}"."notify_stream_status_update"();
13220
13236
  }
13221
13237
 
13222
13238
  // packages/context/src/lib/stream/postgres-notify-change-source.ts
13239
+ var NOT_LISTENING = {
13240
+ release: () => Promise.resolve(),
13241
+ fail: () => {
13242
+ }
13243
+ };
13244
+ var Listening = class {
13245
+ #client;
13246
+ #handlers;
13247
+ constructor(client, handlers) {
13248
+ this.#client = client;
13249
+ this.#handlers = handlers;
13250
+ client.on("notification", handlers.notification);
13251
+ client.on("error", handlers.error);
13252
+ client.on("end", handlers.end);
13253
+ }
13254
+ /** Stops listening and returns the connection to the pool. */
13255
+ async release(channel) {
13256
+ this.#detach();
13257
+ try {
13258
+ await this.#client.query(`UNLISTEN ${quoteIdentifier(channel)}`);
13259
+ } finally {
13260
+ this.#client.release();
13261
+ }
13262
+ }
13263
+ /** Gives a broken connection back for the pool to destroy. */
13264
+ fail(error) {
13265
+ this.#detach();
13266
+ this.#client.release(error);
13267
+ }
13268
+ #detach() {
13269
+ this.#client.off("notification", this.#handlers.notification);
13270
+ this.#client.off("error", this.#handlers.error);
13271
+ this.#client.off("end", this.#handlers.end);
13272
+ }
13273
+ };
13274
+ var ChangeDelivery = class {
13275
+ change;
13276
+ constructor(change) {
13277
+ this.change = change;
13278
+ }
13279
+ };
13223
13280
  var PostgresNotifyChangeSource = class _PostgresNotifyChangeSource {
13224
13281
  #pool;
13225
13282
  #schema;
13226
13283
  #channel;
13227
13284
  #ownsPool;
13228
- #listener;
13229
- #listenPromise;
13230
- #isInitialized = false;
13231
- #isClosing = false;
13232
- #isClosed = false;
13233
- #subscribers = /* @__PURE__ */ new Map();
13234
- #onNotification = (message2) => {
13235
- this.#handleNotification(message2);
13236
- };
13237
- #onListenerError = (error) => {
13238
- this.#failListener(error);
13239
- };
13240
- #onListenerEnd = () => {
13241
- if (!this.#isClosing) {
13242
- this.#failListener(
13243
- new Error("PostgreSQL stream notification listener ended")
13244
- );
13285
+ /** Aborts when close() starts: every subscription ends, and no LISTEN connection opens. */
13286
+ #closed = new AbortController();
13287
+ /** Emits `stream:<id>` with a ChangeDelivery; `error` fails every subscription. */
13288
+ #changes = new EventEmitter().setMaxListeners(0);
13289
+ #subscriptions = 0;
13290
+ #listener = NOT_LISTENING;
13291
+ /** Concurrent subscribers share one LISTEN setup; a failed one runs again. */
13292
+ #listen = pMemoize3(() => this.#openListener(), { cache: false });
13293
+ #handlers = {
13294
+ notification: (message2) => {
13295
+ this.#handleNotification(message2);
13296
+ },
13297
+ error: (error) => {
13298
+ this.#failListener(error);
13299
+ },
13300
+ end: () => {
13301
+ if (!this.#closed.signal.aborted) {
13302
+ this.#failListener(
13303
+ new Error("PostgreSQL stream notification listener ended")
13304
+ );
13305
+ }
13245
13306
  }
13246
13307
  };
13247
13308
  constructor(options) {
@@ -13270,98 +13331,78 @@ var PostgresNotifyChangeSource = class _PostgresNotifyChangeSource {
13270
13331
  );
13271
13332
  }
13272
13333
  }
13273
- async initialize() {
13274
- await this.#pool.query(
13275
- postgresStreamNotifyDDL(this.#schema, this.#channel)
13276
- );
13277
- this.#isInitialized = true;
13334
+ /** Opens the source: installs the notify triggers before it returns. */
13335
+ static async create(options) {
13336
+ const source = new _PostgresNotifyChangeSource(options);
13337
+ try {
13338
+ await source.#pool.query(
13339
+ lockSchemaDDL(
13340
+ postgresStreamNotifyDDL(source.#schema, source.#channel),
13341
+ source.#schema
13342
+ )
13343
+ );
13344
+ } catch (error) {
13345
+ await source.close();
13346
+ throw error;
13347
+ }
13348
+ return source;
13278
13349
  }
13279
13350
  async *subscribe(streamId, signal) {
13280
- this.#ensureInitialized();
13281
- this.#ensureOpen();
13282
- const queue = new ChangeQueue();
13283
- this.#addSubscriber(streamId, queue);
13284
- const cleanup = () => {
13285
- queue.close();
13286
- this.#removeSubscriber(streamId, queue);
13287
- };
13288
- signal.addEventListener("abort", cleanup, { once: true });
13351
+ if (this.#closed.signal.aborted) {
13352
+ throw new Error("PostgresNotifyChangeSource is closed.");
13353
+ }
13354
+ if (signal.aborted) return;
13355
+ const changes = on(this.#changes, `stream:${streamId}`, {
13356
+ signal: AbortSignal.any([signal, this.#closed.signal])
13357
+ });
13358
+ this.#subscriptions += 1;
13289
13359
  try {
13290
13360
  await this.#ensureListening();
13291
- if (signal.aborted || this.#isClosed) return;
13361
+ if (signal.aborted || this.#closed.signal.aborted) return;
13292
13362
  yield { kind: "tick" };
13293
- while (!signal.aborted && !this.#isClosed) {
13294
- const change = await queue.next(signal);
13295
- if (!change) return;
13296
- yield change;
13363
+ for await (const [delivery] of changes) {
13364
+ if (delivery instanceof ChangeDelivery) yield delivery.change;
13297
13365
  }
13366
+ } catch (error) {
13367
+ if (error instanceof Error && error.name === "AbortError") return;
13368
+ throw error;
13298
13369
  } finally {
13299
- signal.removeEventListener("abort", cleanup);
13300
- cleanup();
13370
+ this.#subscriptions -= 1;
13371
+ await changes.return?.();
13301
13372
  await this.#releaseListenerIfIdle();
13302
13373
  }
13303
13374
  }
13304
13375
  async close() {
13305
- if (this.#isClosed) return;
13306
- this.#isClosed = true;
13307
- this.#isClosing = true;
13308
- for (const subscribers of this.#subscribers.values()) {
13309
- for (const subscriber of subscribers) {
13310
- subscriber.close();
13311
- }
13312
- }
13313
- this.#subscribers.clear();
13314
- if (this.#listenPromise) {
13315
- try {
13316
- await this.#listenPromise;
13317
- } catch {
13318
- }
13376
+ if (this.#closed.signal.aborted) return;
13377
+ this.#closed.abort();
13378
+ try {
13379
+ await this.#listen();
13380
+ } catch {
13319
13381
  }
13320
13382
  await this.#releaseListener();
13321
13383
  if (this.#ownsPool) {
13322
13384
  await this.#pool.end();
13323
13385
  }
13324
13386
  }
13325
- #ensureInitialized() {
13326
- if (!this.#isInitialized) {
13327
- throw new Error(
13328
- "PostgresNotifyChangeSource not initialized. Call await source.initialize() after construction."
13329
- );
13330
- }
13387
+ async #ensureListening() {
13388
+ if (this.#listener instanceof Listening) return;
13389
+ await this.#listen();
13331
13390
  }
13332
- #ensureOpen() {
13333
- if (this.#isClosed) {
13334
- throw new Error("PostgresNotifyChangeSource is closed.");
13391
+ async #openListener() {
13392
+ if (this.#closed.signal.aborted) return;
13393
+ const client = await this.#pool.connect();
13394
+ const listening = new Listening(client, this.#handlers);
13395
+ try {
13396
+ await client.query(`LISTEN ${quoteIdentifier(this.#channel)}`);
13397
+ } catch (error) {
13398
+ listening.fail(true);
13399
+ throw error;
13335
13400
  }
13336
- }
13337
- async #ensureListening() {
13338
- this.#ensureOpen();
13339
- if (this.#listener) return;
13340
- if (this.#listenPromise) return this.#listenPromise;
13341
- this.#listenPromise = (async () => {
13342
- const listener = await this.#pool.connect();
13343
- let success = false;
13344
- try {
13345
- listener.on("notification", this.#onNotification);
13346
- listener.on("error", this.#onListenerError);
13347
- listener.on("end", this.#onListenerEnd);
13348
- await listener.query(`LISTEN ${quoteIdentifier(this.#channel)}`);
13349
- if (this.#isClosed) {
13350
- return;
13351
- }
13352
- this.#listener = listener;
13353
- success = true;
13354
- } finally {
13355
- this.#listenPromise = void 0;
13356
- if (!success) {
13357
- listener.off("notification", this.#onNotification);
13358
- listener.off("error", this.#onListenerError);
13359
- listener.off("end", this.#onListenerEnd);
13360
- listener.release(true);
13361
- }
13362
- }
13363
- })();
13364
- return this.#listenPromise;
13401
+ if (this.#closed.signal.aborted) {
13402
+ listening.fail(true);
13403
+ return;
13404
+ }
13405
+ this.#listener = listening;
13365
13406
  }
13366
13407
  #handleNotification(message2) {
13367
13408
  if (message2.channel !== this.#channel || !message2.payload) return;
@@ -13369,125 +13410,28 @@ var PostgresNotifyChangeSource = class _PostgresNotifyChangeSource {
13369
13410
  if (!payload) return;
13370
13411
  if (payload.schema !== this.#schema) return;
13371
13412
  if (payload.kind !== "chunks" && payload.kind !== "status") return;
13372
- const subscribers = this.#subscribers.get(payload.streamId);
13373
- if (!subscribers) return;
13374
13413
  const change = payload.kind === "chunks" ? { kind: "chunks" } : { kind: "status" };
13375
- for (const subscriber of subscribers) {
13376
- subscriber.push(change);
13377
- }
13414
+ this.#changes.emit(
13415
+ `stream:${payload.streamId}`,
13416
+ new ChangeDelivery(change)
13417
+ );
13378
13418
  }
13379
13419
  #failListener(error) {
13380
13420
  const listener = this.#listener;
13381
- this.#listener = void 0;
13382
- this.#listenPromise = void 0;
13383
- if (listener) {
13384
- listener.off("notification", this.#onNotification);
13385
- listener.off("error", this.#onListenerError);
13386
- listener.off("end", this.#onListenerEnd);
13387
- listener.release(error);
13388
- }
13389
- for (const subscribers of this.#subscribers.values()) {
13390
- for (const subscriber of subscribers) {
13391
- subscriber.fail(error);
13392
- }
13421
+ this.#listener = NOT_LISTENING;
13422
+ listener.fail(error);
13423
+ if (this.#changes.listenerCount("error") > 0) {
13424
+ this.#changes.emit("error", error);
13393
13425
  }
13394
- this.#subscribers.clear();
13395
13426
  }
13396
13427
  async #releaseListenerIfIdle() {
13397
- if (this.#subscribers.size > 0) return;
13428
+ if (this.#subscriptions > 0) return;
13398
13429
  await this.#releaseListener();
13399
13430
  }
13400
13431
  async #releaseListener() {
13401
13432
  const listener = this.#listener;
13402
- if (!listener) return;
13403
- this.#listener = void 0;
13404
- listener.off("notification", this.#onNotification);
13405
- listener.off("error", this.#onListenerError);
13406
- listener.off("end", this.#onListenerEnd);
13407
- try {
13408
- await listener.query(`UNLISTEN ${quoteIdentifier(this.#channel)}`);
13409
- } finally {
13410
- listener.release();
13411
- }
13412
- }
13413
- #addSubscriber(streamId, subscriber) {
13414
- let subscribers = this.#subscribers.get(streamId);
13415
- if (!subscribers) {
13416
- subscribers = /* @__PURE__ */ new Set();
13417
- this.#subscribers.set(streamId, subscribers);
13418
- }
13419
- subscribers.add(subscriber);
13420
- }
13421
- #removeSubscriber(streamId, subscriber) {
13422
- const subscribers = this.#subscribers.get(streamId);
13423
- if (!subscribers) return;
13424
- subscribers.delete(subscriber);
13425
- if (subscribers.size === 0) {
13426
- this.#subscribers.delete(streamId);
13427
- }
13428
- }
13429
- };
13430
- var ChangeQueue = class {
13431
- #values = [];
13432
- #waiters = /* @__PURE__ */ new Set();
13433
- #closed = false;
13434
- #error;
13435
- push(value) {
13436
- if (this.#closed) return;
13437
- const waiter = this.#waiters.values().next().value;
13438
- if (waiter) {
13439
- this.#waiters.delete(waiter);
13440
- waiter.resolve(value);
13441
- return;
13442
- }
13443
- this.#values.push(value);
13444
- }
13445
- fail(error) {
13446
- if (this.#closed) return;
13447
- this.#closed = true;
13448
- this.#error = error;
13449
- this.#values = [];
13450
- for (const waiter of this.#waiters) {
13451
- waiter.reject(error);
13452
- }
13453
- this.#waiters.clear();
13454
- }
13455
- close() {
13456
- if (this.#closed) return;
13457
- this.#closed = true;
13458
- this.#values = [];
13459
- for (const waiter of this.#waiters) {
13460
- waiter.resolve(void 0);
13461
- }
13462
- this.#waiters.clear();
13463
- }
13464
- async next(signal) {
13465
- if (this.#error) throw this.#error;
13466
- if (this.#closed || signal.aborted) return void 0;
13467
- if (this.#values.length > 0) {
13468
- return this.#values.shift();
13469
- }
13470
- return new Promise((resolve3, reject) => {
13471
- const waiter = { resolve: resolve3, reject };
13472
- const cleanup = () => {
13473
- this.#waiters.delete(waiter);
13474
- signal.removeEventListener("abort", onAbort);
13475
- };
13476
- const onAbort = () => {
13477
- cleanup();
13478
- resolve3(void 0);
13479
- };
13480
- waiter.resolve = (value) => {
13481
- cleanup();
13482
- resolve3(value);
13483
- };
13484
- waiter.reject = (error) => {
13485
- cleanup();
13486
- reject(error);
13487
- };
13488
- this.#waiters.add(waiter);
13489
- signal.addEventListener("abort", onAbort, { once: true });
13490
- });
13433
+ this.#listener = NOT_LISTENING;
13434
+ await listener.release(this.#channel);
13491
13435
  }
13492
13436
  };
13493
13437
  var notificationPayload = z16.object({
@@ -13610,8 +13554,6 @@ var PostgresStreamStore = class _PostgresStreamStore extends StreamStore {
13610
13554
  #pool;
13611
13555
  #schema;
13612
13556
  #ownsPool;
13613
- #isInitialized = false;
13614
- #isClosed = false;
13615
13557
  constructor(options) {
13616
13558
  super();
13617
13559
  const schema = options.schema ?? "public";
@@ -13639,26 +13581,28 @@ var PostgresStreamStore = class _PostgresStreamStore extends StreamStore {
13639
13581
  #t(name) {
13640
13582
  return `"${this.#schema}"."${name}"`;
13641
13583
  }
13642
- async initialize() {
13643
- await this.#pool.query(postgresStreamDDL(this.#schema));
13644
- this.#isInitialized = true;
13584
+ /** Opens the store: creates its tables before it returns. */
13585
+ static async create(options) {
13586
+ const store = new _PostgresStreamStore(options);
13587
+ try {
13588
+ await store.#initialize();
13589
+ } catch (error) {
13590
+ await store.close();
13591
+ throw error;
13592
+ }
13593
+ return store;
13594
+ }
13595
+ async #initialize() {
13596
+ await this.#pool.query(
13597
+ lockSchemaDDL(postgresStreamDDL(this.#schema), this.#schema)
13598
+ );
13645
13599
  }
13646
13600
  async close() {
13647
- if (this.#isClosed) return;
13648
- this.#isClosed = true;
13649
- if (this.#ownsPool) {
13601
+ if (this.#ownsPool && !this.#pool.ending) {
13650
13602
  await this.#pool.end();
13651
13603
  }
13652
13604
  }
13653
- #ensureInitialized() {
13654
- if (!this.#isInitialized) {
13655
- throw new Error(
13656
- "PostgresStreamStore not initialized. Call await store.initialize() after construction."
13657
- );
13658
- }
13659
- }
13660
13605
  async #query(sql, params) {
13661
- this.#ensureInitialized();
13662
13606
  const result = await this.#pool.query(sql, params);
13663
13607
  return result.rows;
13664
13608
  }
@@ -13666,7 +13610,6 @@ var PostgresStreamStore = class _PostgresStreamStore extends StreamStore {
13666
13610
  return z18.array(row).parse(await this.#query(sql, params));
13667
13611
  }
13668
13612
  async #useTransaction(fn) {
13669
- this.#ensureInitialized();
13670
13613
  const client = await this.#pool.connect();
13671
13614
  try {
13672
13615
  await client.query("BEGIN");
@@ -13945,7 +13888,10 @@ function assertIdentifier2(value, label) {
13945
13888
  }
13946
13889
 
13947
13890
  // packages/context/src/lib/stream/sqlite.stream-store.ts
13948
- import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
13891
+ import { transaction as transaction2 } from "@zukhruf/sqlite-transaction";
13892
+ import {
13893
+ DatabaseSync as DatabaseSync2
13894
+ } from "node:sqlite";
13949
13895
  import { z as z19 } from "zod";
13950
13896
 
13951
13897
  // packages/context/src/lib/stream/ddl.stream.sqlite.sql
@@ -13971,57 +13917,27 @@ var chunkRow2 = z19.object({
13971
13917
  });
13972
13918
  var SqliteStreamStore = class extends StreamStore {
13973
13919
  #db;
13974
- #statements = /* @__PURE__ */ new Map();
13975
- #closed = false;
13976
- #stmt(sql) {
13977
- let stmt = this.#statements.get(sql);
13978
- if (!stmt) {
13979
- stmt = this.#db.prepare(sql);
13980
- this.#statements.set(sql, stmt);
13981
- }
13982
- return stmt;
13983
- }
13920
+ #sql;
13984
13921
  close() {
13985
- if (this.#closed) return;
13986
- this.#closed = true;
13987
- this.#statements.clear();
13922
+ if (!this.#db.isOpen) return;
13988
13923
  this.#db.close();
13989
13924
  }
13990
13925
  constructor(pathOrDb) {
13991
13926
  super();
13992
13927
  this.#db = typeof pathOrDb === "string" ? new DatabaseSync2(pathOrDb) : pathOrDb;
13993
13928
  this.#db.exec(ddl_stream_sqlite_default);
13929
+ this.#sql = this.#db.createTagStore();
13994
13930
  }
13995
13931
  async createStream(stream) {
13996
- this.#stmt(
13997
- `INSERT INTO streams (id, status, createdAt, startedAt, finishedAt, cancelRequestedAt, error)
13998
- VALUES (?, ?, ?, ?, ?, ?, ?)`
13999
- ).run(
14000
- stream.id,
14001
- stream.status,
14002
- stream.createdAt,
14003
- stream.startedAt,
14004
- stream.finishedAt,
14005
- stream.cancelRequestedAt,
14006
- stream.error
14007
- );
13932
+ this.#sql.run`INSERT INTO streams (id, status, createdAt, startedAt, finishedAt, cancelRequestedAt, error)
13933
+ VALUES (${stream.id}, ${stream.status}, ${stream.createdAt}, ${stream.startedAt}, ${stream.finishedAt}, ${stream.cancelRequestedAt}, ${stream.error})`;
14008
13934
  }
14009
13935
  async upsertStream(stream) {
14010
13936
  const created = streamRow.optional().parse(
14011
- this.#stmt(
14012
- `INSERT INTO streams (id, status, createdAt, startedAt, finishedAt, cancelRequestedAt, error)
14013
- VALUES (?, ?, ?, ?, ?, ?, ?)
13937
+ this.#sql.get`INSERT INTO streams (id, status, createdAt, startedAt, finishedAt, cancelRequestedAt, error)
13938
+ VALUES (${stream.id}, ${stream.status}, ${stream.createdAt}, ${stream.startedAt}, ${stream.finishedAt}, ${stream.cancelRequestedAt}, ${stream.error})
14014
13939
  ON CONFLICT(id) DO NOTHING
14015
13940
  RETURNING *`
14016
- ).get(
14017
- stream.id,
14018
- stream.status,
14019
- stream.createdAt,
14020
- stream.startedAt,
14021
- stream.finishedAt,
14022
- stream.cancelRequestedAt,
14023
- stream.error
14024
- )
14025
13941
  );
14026
13942
  if (created) {
14027
13943
  return { stream: created, created: true };
@@ -14035,137 +13951,107 @@ var SqliteStreamStore = class extends StreamStore {
14035
13951
  return { stream: existing, created: false };
14036
13952
  }
14037
13953
  async getStream(streamId) {
14038
- return streamRow.optional().parse(this.#stmt("SELECT * FROM streams WHERE id = ?").get(streamId));
13954
+ return streamRow.optional().parse(this.#sql.get`SELECT * FROM streams WHERE id = ${streamId}`);
14039
13955
  }
14040
13956
  async getStreamStatus(streamId) {
14041
- const row = statusRow2.optional().parse(
14042
- this.#stmt("SELECT status FROM streams WHERE id = ?").get(streamId)
14043
- );
13957
+ const row = statusRow2.optional().parse(this.#sql.get`SELECT status FROM streams WHERE id = ${streamId}`);
14044
13958
  return row?.status;
14045
13959
  }
14046
13960
  async listStreamIds(options) {
14047
- let sql = "SELECT id FROM streams";
14048
- const params = [];
14049
- if (options?.status) {
14050
- sql += " WHERE status = ?";
14051
- params.push(options.status);
14052
- }
14053
- sql += " ORDER BY createdAt ASC, id ASC";
14054
- const rows = z19.array(idRow5).parse(this.#stmt(sql).all(...params));
13961
+ const rows = z19.array(idRow5).parse(
13962
+ options?.status ? this.#sql.all`SELECT id FROM streams WHERE status = ${options.status} ORDER BY createdAt ASC, id ASC` : this.#sql.all`SELECT id FROM streams ORDER BY createdAt ASC, id ASC`
13963
+ );
14055
13964
  return rows.map((row) => row.id);
14056
13965
  }
14057
13966
  async updateStream(streamId, update) {
14058
- return this.#transaction(() => {
14059
- const stream = streamRow.optional().parse(this.#stmt("SELECT * FROM streams WHERE id = ?").get(streamId));
14060
- if (!stream) {
14061
- throw new Error(`updateStream: stream "${streamId}" not found`);
14062
- }
14063
- const updates = update(stream);
14064
- if (updates === void 0) return { stream, updated: false };
14065
- const setClauses = [];
14066
- const params = [];
14067
- const set = (column, value) => {
14068
- setClauses.push(`${column} = ?`);
14069
- params.push(value);
14070
- };
14071
- if (updates.status !== void 0) set("status", updates.status);
14072
- if (updates.startedAt !== void 0) set("startedAt", updates.startedAt);
14073
- if (updates.finishedAt !== void 0) {
14074
- set("finishedAt", updates.finishedAt);
14075
- }
14076
- if (updates.cancelRequestedAt !== void 0) {
14077
- set("cancelRequestedAt", updates.cancelRequestedAt);
14078
- }
14079
- if (updates.error !== void 0) set("error", updates.error);
14080
- if (setClauses.length === 0) return { stream, updated: false };
14081
- params.push(streamId);
14082
- const next = streamRow.parse(
14083
- this.#stmt(
14084
- `UPDATE streams SET ${setClauses.join(", ")} WHERE id = ? RETURNING *`
14085
- ).get(...params)
14086
- );
14087
- return { stream: next, updated: true };
14088
- });
13967
+ return transaction2(
13968
+ this.#db,
13969
+ () => {
13970
+ const stream = streamRow.optional().parse(this.#sql.get`SELECT * FROM streams WHERE id = ${streamId}`);
13971
+ if (!stream) {
13972
+ throw new Error(`updateStream: stream "${streamId}" not found`);
13973
+ }
13974
+ const updates = update(stream);
13975
+ if (updates === void 0) return { stream, updated: false };
13976
+ const setClauses = [];
13977
+ const params = [];
13978
+ const set = (column, value) => {
13979
+ setClauses.push(`${column} = ?`);
13980
+ params.push(value);
13981
+ };
13982
+ if (updates.status !== void 0) set("status", updates.status);
13983
+ if (updates.startedAt !== void 0)
13984
+ set("startedAt", updates.startedAt);
13985
+ if (updates.finishedAt !== void 0) {
13986
+ set("finishedAt", updates.finishedAt);
13987
+ }
13988
+ if (updates.cancelRequestedAt !== void 0) {
13989
+ set("cancelRequestedAt", updates.cancelRequestedAt);
13990
+ }
13991
+ if (updates.error !== void 0) set("error", updates.error);
13992
+ if (setClauses.length === 0) return { stream, updated: false };
13993
+ params.push(streamId);
13994
+ const next = streamRow.parse(
13995
+ this.#db.prepare(
13996
+ `UPDATE streams SET ${setClauses.join(", ")} WHERE id = ? RETURNING *`
13997
+ ).get(...params)
13998
+ );
13999
+ return { stream: next, updated: true };
14000
+ },
14001
+ { mode: "immediate" }
14002
+ );
14089
14003
  }
14090
14004
  async updateStreamStatus(streamId, status, options) {
14091
14005
  const now = Date.now();
14092
14006
  switch (status) {
14093
14007
  case "running":
14094
- this.#stmt(
14095
- `UPDATE streams SET status = ?, startedAt = ?
14096
- WHERE id = ? AND status = 'queued'`
14097
- ).run(status, now, streamId);
14008
+ this.#sql.run`UPDATE streams SET status = ${status}, startedAt = ${now}
14009
+ WHERE id = ${streamId} AND status = 'queued'`;
14098
14010
  break;
14099
14011
  case "completed":
14100
- this.#stmt(
14101
- `UPDATE streams SET status = ?, finishedAt = ?
14102
- WHERE id = ? AND status IN ('queued', 'running')`
14103
- ).run(status, now, streamId);
14012
+ this.#sql.run`UPDATE streams SET status = ${status}, finishedAt = ${now}
14013
+ WHERE id = ${streamId} AND status IN ('queued', 'running')`;
14104
14014
  break;
14105
14015
  case "failed":
14106
- this.#stmt(
14107
- `UPDATE streams SET status = ?, finishedAt = ?, error = ?
14108
- WHERE id = ? AND status IN ('queued', 'running')`
14109
- ).run(status, now, options?.error ?? null, streamId);
14016
+ this.#sql.run`UPDATE streams SET status = ${status}, finishedAt = ${now}, error = ${options?.error ?? null}
14017
+ WHERE id = ${streamId} AND status IN ('queued', 'running')`;
14110
14018
  break;
14111
14019
  case "cancelled":
14112
- this.#stmt(
14113
- `UPDATE streams SET status = ?, cancelRequestedAt = ?, finishedAt = ?
14114
- WHERE id = ? AND status IN ('queued', 'running')`
14115
- ).run(status, now, now, streamId);
14020
+ this.#sql.run`UPDATE streams SET status = ${status}, cancelRequestedAt = ${now}, finishedAt = ${now}
14021
+ WHERE id = ${streamId} AND status IN ('queued', 'running')`;
14116
14022
  break;
14117
14023
  default:
14118
- this.#stmt(
14119
- `UPDATE streams SET status = ?
14120
- WHERE id = ? AND status = 'queued'`
14121
- ).run(status, streamId);
14024
+ this.#sql.run`UPDATE streams SET status = ${status}
14025
+ WHERE id = ${streamId} AND status = 'queued'`;
14122
14026
  }
14123
14027
  }
14124
14028
  async appendChunks(chunks) {
14125
14029
  if (chunks.length === 0) return;
14126
14030
  const failures = collectStreamFailures(chunks);
14127
- this.#transaction(() => {
14128
- for (const chunk of chunks) {
14129
- this.#stmt(
14130
- `INSERT INTO stream_chunks (streamId, seq, data, createdAt)
14131
- VALUES (?, ?, ?, ?)`
14132
- ).run(
14133
- chunk.streamId,
14134
- chunk.seq,
14135
- JSON.stringify(chunk.data),
14136
- chunk.createdAt
14137
- );
14138
- }
14139
- const failedAt = Date.now();
14140
- for (const failure of failures) {
14141
- const result = this.#stmt(
14142
- `UPDATE streams SET status = ?, finishedAt = ?, error = ?
14143
- WHERE id = ? AND status IN ('queued', 'running')`
14144
- ).run("failed", failedAt, failure.error, failure.streamId);
14145
- if (result.changes !== 1) {
14146
- const existing = this.#stmt(
14147
- "SELECT id FROM streams WHERE id = ?"
14148
- ).get(failure.streamId);
14149
- if (!existing) {
14150
- throw new Error(`Stream "${failure.streamId}" not found`);
14031
+ transaction2(
14032
+ this.#db,
14033
+ () => {
14034
+ for (const chunk of chunks) {
14035
+ this.#sql.run`INSERT INTO stream_chunks (streamId, seq, data, createdAt)
14036
+ VALUES (${chunk.streamId}, ${chunk.seq}, ${JSON.stringify(chunk.data)}, ${chunk.createdAt})`;
14037
+ }
14038
+ const failedAt = Date.now();
14039
+ for (const failure of failures) {
14040
+ const result = this.#sql.run`UPDATE streams SET status = 'failed', finishedAt = ${failedAt}, error = ${failure.error}
14041
+ WHERE id = ${failure.streamId} AND status IN ('queued', 'running')`;
14042
+ if (result.changes !== 1) {
14043
+ const existing = this.#sql.get`SELECT id FROM streams WHERE id = ${failure.streamId}`;
14044
+ if (!existing) {
14045
+ throw new Error(`Stream "${failure.streamId}" not found`);
14046
+ }
14151
14047
  }
14152
14048
  }
14153
- }
14154
- });
14049
+ },
14050
+ { mode: "immediate" }
14051
+ );
14155
14052
  }
14156
14053
  async getChunks(streamId, fromSeq, limit) {
14157
- let sql = "SELECT * FROM stream_chunks WHERE streamId = ?";
14158
- const params = [streamId];
14159
- if (fromSeq !== void 0) {
14160
- sql += " AND seq >= ?";
14161
- params.push(fromSeq);
14162
- }
14163
- sql += " ORDER BY seq ASC";
14164
- if (limit !== void 0) {
14165
- sql += " LIMIT ?";
14166
- params.push(limit);
14167
- }
14168
- const rows = z19.array(chunkRow2).parse(this.#stmt(sql).all(...params));
14054
+ const rows = z19.array(chunkRow2).parse(this.#chunkRows(streamId, fromSeq, limit));
14169
14055
  return Promise.all(
14170
14056
  rows.map(async (row) => ({
14171
14057
  streamId: row.streamId,
@@ -14175,50 +14061,50 @@ var SqliteStreamStore = class extends StreamStore {
14175
14061
  }))
14176
14062
  );
14177
14063
  }
14064
+ #chunkRows(streamId, fromSeq, limit) {
14065
+ if (fromSeq !== void 0 && limit !== void 0) {
14066
+ return this.#sql.all`SELECT * FROM stream_chunks WHERE streamId = ${streamId} AND seq >= ${fromSeq} ORDER BY seq ASC LIMIT ${limit}`;
14067
+ }
14068
+ if (fromSeq !== void 0) {
14069
+ return this.#sql.all`SELECT * FROM stream_chunks WHERE streamId = ${streamId} AND seq >= ${fromSeq} ORDER BY seq ASC`;
14070
+ }
14071
+ if (limit !== void 0) {
14072
+ return this.#sql.all`SELECT * FROM stream_chunks WHERE streamId = ${streamId} ORDER BY seq ASC LIMIT ${limit}`;
14073
+ }
14074
+ return this.#sql.all`SELECT * FROM stream_chunks WHERE streamId = ${streamId} ORDER BY seq ASC`;
14075
+ }
14178
14076
  async deleteStream(streamId) {
14179
- this.#stmt("DELETE FROM streams WHERE id = ?").run(streamId);
14077
+ this.#sql.run`DELETE FROM streams WHERE id = ${streamId}`;
14180
14078
  }
14181
14079
  async reopenStream(streamId) {
14182
- return this.#transaction(() => {
14183
- const row = statusRow2.optional().parse(this.#stmt("SELECT * FROM streams WHERE id = ?").get(streamId));
14184
- if (!row) {
14185
- throw new Error(`Stream "${streamId}" not found`);
14186
- }
14187
- if (row.status !== "completed" && row.status !== "failed" && row.status !== "cancelled") {
14188
- throw new Error(
14189
- `Cannot reopen stream "${streamId}" with status "${row.status}". Only terminal streams can be reopened.`
14190
- );
14191
- }
14192
- this.#stmt("DELETE FROM streams WHERE id = ?").run(streamId);
14193
- const now = Date.now();
14194
- this.#stmt(
14195
- `INSERT INTO streams (id, status, createdAt, startedAt, finishedAt, cancelRequestedAt, error)
14196
- VALUES (?, ?, ?, ?, ?, ?, ?)`
14197
- ).run(streamId, "queued", now, null, null, null, null);
14198
- return {
14199
- id: streamId,
14200
- status: "queued",
14201
- createdAt: now,
14202
- startedAt: null,
14203
- finishedAt: null,
14204
- cancelRequestedAt: null,
14205
- error: null
14206
- };
14207
- });
14208
- }
14209
- #transaction(callback) {
14210
- this.#db.exec("BEGIN IMMEDIATE");
14211
- try {
14212
- const result = callback();
14213
- this.#db.exec("COMMIT");
14214
- return result;
14215
- } catch (error) {
14216
- try {
14217
- this.#db.exec("ROLLBACK");
14218
- } catch {
14219
- }
14220
- throw error;
14221
- }
14080
+ return transaction2(
14081
+ this.#db,
14082
+ () => {
14083
+ const row = statusRow2.optional().parse(this.#sql.get`SELECT * FROM streams WHERE id = ${streamId}`);
14084
+ if (!row) {
14085
+ throw new Error(`Stream "${streamId}" not found`);
14086
+ }
14087
+ if (row.status !== "completed" && row.status !== "failed" && row.status !== "cancelled") {
14088
+ throw new Error(
14089
+ `Cannot reopen stream "${streamId}" with status "${row.status}". Only terminal streams can be reopened.`
14090
+ );
14091
+ }
14092
+ this.#sql.run`DELETE FROM streams WHERE id = ${streamId}`;
14093
+ const now = Date.now();
14094
+ this.#sql.run`INSERT INTO streams (id, status, createdAt, startedAt, finishedAt, cancelRequestedAt, error)
14095
+ VALUES (${streamId}, 'queued', ${now}, NULL, NULL, NULL, NULL)`;
14096
+ return {
14097
+ id: streamId,
14098
+ status: "queued",
14099
+ createdAt: now,
14100
+ startedAt: null,
14101
+ finishedAt: null,
14102
+ cancelRequestedAt: null,
14103
+ error: null
14104
+ };
14105
+ },
14106
+ { mode: "immediate" }
14107
+ );
14222
14108
  }
14223
14109
  };
14224
14110
 
@@ -14684,8 +14570,8 @@ export {
14684
14570
  PostgresContextStore,
14685
14571
  PostgresNotifyChangeSource,
14686
14572
  PostgresStreamStore,
14687
- PromiseResolver,
14688
14573
  RuntimeStrategy,
14574
+ SearchQuerySyntaxError,
14689
14575
  SqlServerContextStore,
14690
14576
  SqliteContextStore,
14691
14577
  SqliteStreamStore,
@@ -14792,6 +14678,7 @@ export {
14792
14678
  isSyntheticReminderMessage,
14793
14679
  lastAssistantLength,
14794
14680
  localeReminder,
14681
+ lockSchemaDDL,
14795
14682
  mapGenerateErrorToCode,
14796
14683
  mergeMessageMetadata,
14797
14684
  mergeReminderMetadata,