@deepagents/context 4.2.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2616,7 +2616,15 @@ async function evaluateFiredReminders(configs, whenCtx) {
2616
2616
  }
2617
2617
 
2618
2618
  // packages/context/src/lib/save/save-pipeline.ts
2619
- var SavePipeline = class {
2619
+ var HeadConflictError = class extends Error {
2620
+ constructor(chatId, headMessageId) {
2621
+ super(
2622
+ `Could not commit head "${headMessageId}" for chat "${chatId}": concurrent writers kept moving the branch head`
2623
+ );
2624
+ this.name = "HeadConflictError";
2625
+ }
2626
+ };
2627
+ var SavePipeline = class _SavePipeline {
2620
2628
  #engine;
2621
2629
  #pending;
2622
2630
  #shouldBranch = true;
@@ -2672,9 +2680,49 @@ var SavePipeline = class {
2672
2680
  );
2673
2681
  }
2674
2682
  await this.#engine.store.addMessages(messages);
2675
- await this.#engine.commitHead(parentId);
2683
+ await this.#commitHead(messages[0], parentId);
2676
2684
  return { headMessageId: parentId };
2677
2685
  }
2686
+ static #maxCommitAttempts = 5;
2687
+ /**
2688
+ * Advance the branch head to `newHead` through the store's CAS. A stale
2689
+ * cached head must never rewind the branch (that silently orphans turns
2690
+ * committed in between), so a rejected CAS re-reads the store head and
2691
+ * reconciles:
2692
+ * - the store head already descends from `newHead` (an in-place assistant
2693
+ * fill whose turn was built past): content is on the chain, keep the
2694
+ * store head;
2695
+ * - `newHead` descends from the store head: fast-forward, retry against it;
2696
+ * - true fork (another turn won the same head): graft this turn onto the
2697
+ * winning head by re-parenting the first pending message, then retry.
2698
+ * Grafting applies only to messages chained off the stale head — an
2699
+ * in-place fill keeps its original parent and its content already landed.
2700
+ */
2701
+ async #commitHead(first2, newHead) {
2702
+ let expected = this.#engine.getActiveBranch().headMessageId;
2703
+ for (let i = 0; i < _SavePipeline.#maxCommitAttempts; i++) {
2704
+ if (await this.#engine.commitHead(newHead, expected)) return;
2705
+ const fresh = (await this.#engine.refreshBranch()).headMessageId;
2706
+ if (fresh === null || fresh === expected) {
2707
+ expected = fresh;
2708
+ continue;
2709
+ }
2710
+ if (await this.#chainContains(fresh, newHead)) return;
2711
+ if (await this.#chainContains(newHead, fresh)) {
2712
+ expected = fresh;
2713
+ continue;
2714
+ }
2715
+ if (first2.parentId !== expected) return;
2716
+ await this.#engine.store.setMessageParent(first2.id, fresh);
2717
+ first2.parentId = fresh;
2718
+ expected = fresh;
2719
+ }
2720
+ throw new HeadConflictError(this.#engine.chatId, newHead);
2721
+ }
2722
+ async #chainContains(headId, messageId) {
2723
+ const chain = await this.#engine.store.getMessageChain(headId);
2724
+ return chain.some((message2) => message2.id === messageId);
2725
+ }
2678
2726
  };
2679
2727
 
2680
2728
  // packages/context/src/lib/store/sqlite.store.ts
@@ -2942,6 +2990,17 @@ var SqliteContextStore = class extends ContextStore {
2942
2990
  }
2943
2991
  });
2944
2992
  }
2993
+ async setMessageParent(messageId, parentId) {
2994
+ if (parentId === messageId) {
2995
+ throw new Error(`Message ${messageId} cannot be its own parent`);
2996
+ }
2997
+ const result = this.#stmt(
2998
+ "UPDATE messages SET parentId = ? WHERE id = ?"
2999
+ ).run(parentId, messageId);
3000
+ if (result.changes === 0) {
3001
+ throw new Error(`Message ${messageId} not found`);
3002
+ }
3003
+ }
2945
3004
  async getMessage(messageId) {
2946
3005
  const row = this.#stmt("SELECT * FROM messages WHERE id = ?").get(
2947
3006
  messageId
@@ -3048,11 +3107,11 @@ var SqliteContextStore = class extends ContextStore {
3048
3107
  this.#db.prepare("UPDATE branches SET isActive = 0 WHERE chatId = ?").run(chatId);
3049
3108
  this.#db.prepare("UPDATE branches SET isActive = 1 WHERE id = ?").run(branchId);
3050
3109
  }
3051
- async updateBranchHead(branchId, messageId) {
3052
- this.#stmt("UPDATE branches SET headMessageId = ? WHERE id = ?").run(
3053
- messageId,
3054
- branchId
3055
- );
3110
+ async updateBranchHead(branchId, messageId, expectedHeadMessageId) {
3111
+ const result = this.#stmt(
3112
+ "UPDATE branches SET headMessageId = ? WHERE id = ? AND headMessageId IS ?"
3113
+ ).run(messageId, branchId, expectedHeadMessageId);
3114
+ return result.changes > 0;
3056
3115
  }
3057
3116
  async listBranches(chatId) {
3058
3117
  const rows = this.#db.prepare(
@@ -3963,12 +4022,29 @@ var ContextEngine = class _ContextEngine {
3963
4022
  id: this.#activeBranch.id,
3964
4023
  headMessageId: this.#activeBranch.headMessageId
3965
4024
  }),
3966
- commitHead: async (headMessageId) => {
3967
- await this.#store.updateBranchHead(
4025
+ commitHead: async (headMessageId, expectedHeadMessageId) => {
4026
+ const committed = await this.#store.updateBranchHead(
3968
4027
  this.#activeBranch.id,
3969
- headMessageId
4028
+ headMessageId,
4029
+ expectedHeadMessageId
3970
4030
  );
3971
- this.#activeBranch.headMessageId = headMessageId;
4031
+ if (committed) {
4032
+ this.#activeBranch.headMessageId = headMessageId;
4033
+ }
4034
+ return committed;
4035
+ },
4036
+ refreshBranch: async () => {
4037
+ const fresh = await this.#store.getBranch(
4038
+ this.#chatId,
4039
+ this.#activeBranch.name
4040
+ );
4041
+ if (!fresh) {
4042
+ throw new Error(
4043
+ `Branch "${this.#activeBranch.name}" not found for chat "${this.#chatId}"`
4044
+ );
4045
+ }
4046
+ this.#branch = fresh;
4047
+ return { id: fresh.id, headMessageId: fresh.headMessageId };
3972
4048
  },
3973
4049
  rewindForUpdate: (parentId) => this.#rewindForUpdate(parentId)
3974
4050
  };
@@ -4069,8 +4145,14 @@ var ContextEngine = class _ContextEngine {
4069
4145
  *
4070
4146
  * @example
4071
4147
  * ```ts
4072
- * context.set(user('What is 2 + 2?', { id: 'q1' }));
4073
- * context.set(assistant('The answer is 5.', { id: 'wrong' })); // Oops!
4148
+ * context.set(
4149
+ * user({
4150
+ * id: 'q1',
4151
+ * role: 'user',
4152
+ * parts: [{ type: 'text', text: 'What is 2 + 2?' }],
4153
+ * }),
4154
+ * );
4155
+ * context.set(assistantText('The answer is 5.', { id: 'wrong' })); // Oops!
4074
4156
  * await context.save();
4075
4157
  *
4076
4158
  * // Rewind to the question, creates new branch
@@ -4908,7 +4990,7 @@ async function chat(agent2, options = {}) {
4908
4990
  execute: async ({ writer }) => {
4909
4991
  writer.merge(uiStream);
4910
4992
  if (title) {
4911
- writer.write({ type: "data-chat-title", data: title });
4993
+ writer.write({ type: "data-chat-title", data: title, transient: true });
4912
4994
  }
4913
4995
  }
4914
4996
  });
@@ -7168,7 +7250,6 @@ var appleEngine = {
7168
7250
  inspectArgs(containerId) {
7169
7251
  return ["inspect", containerId];
7170
7252
  },
7171
- mountArg: buildMountArg,
7172
7253
  parseStatus(stdout) {
7173
7254
  const entries = safeParseArray(stdout);
7174
7255
  if (entries.length === 0) return "absent";
@@ -8428,7 +8509,6 @@ var dockerEngine = {
8428
8509
  containerId
8429
8510
  ];
8430
8511
  },
8431
- mountArg: dockerMountArg,
8432
8512
  parseStatus(status) {
8433
8513
  return status === "running" ? "running" : "stopped";
8434
8514
  },
@@ -10204,6 +10284,18 @@ var PostgresContextStore = class _PostgresContextStore extends ContextStore {
10204
10284
  );
10205
10285
  });
10206
10286
  }
10287
+ async setMessageParent(messageId, parentId) {
10288
+ if (parentId === messageId) {
10289
+ throw new Error(`Message ${messageId} cannot be its own parent`);
10290
+ }
10291
+ const rows = await this.#query(
10292
+ `UPDATE ${this.#t("messages")} SET parentId = $1 WHERE id = $2 RETURNING id`,
10293
+ [parentId, messageId]
10294
+ );
10295
+ if (rows.length === 0) {
10296
+ throw new Error(`Message ${messageId} not found`);
10297
+ }
10298
+ }
10207
10299
  async getMessage(messageId) {
10208
10300
  const rows = await this.#query(`SELECT * FROM ${this.#t("messages")} WHERE id = $1`, [messageId]);
10209
10301
  if (rows.length === 0) {
@@ -10326,11 +10418,14 @@ var PostgresContextStore = class _PostgresContextStore extends ContextStore {
10326
10418
  );
10327
10419
  });
10328
10420
  }
10329
- async updateBranchHead(branchId, messageId) {
10330
- await this.#query(
10331
- `UPDATE ${this.#t("branches")} SET headMessageId = $1 WHERE id = $2`,
10332
- [messageId, branchId]
10421
+ async updateBranchHead(branchId, messageId, expectedHeadMessageId) {
10422
+ const rows = await this.#query(
10423
+ `UPDATE ${this.#t("branches")} SET headMessageId = $1
10424
+ WHERE id = $2 AND headMessageId IS NOT DISTINCT FROM $3
10425
+ RETURNING id`,
10426
+ [messageId, branchId, expectedHeadMessageId]
10333
10427
  );
10428
+ return rows.length > 0;
10334
10429
  }
10335
10430
  async listBranches(chatId) {
10336
10431
  const branches = await this.#query(
@@ -10993,6 +11088,20 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
10993
11088
  `);
10994
11089
  });
10995
11090
  }
11091
+ async setMessageParent(messageId, parentId) {
11092
+ if (parentId === messageId) {
11093
+ throw new Error(`Message ${messageId} cannot be its own parent`);
11094
+ }
11095
+ const rows = await this.#query(
11096
+ `UPDATE ${this.#t("messages")} SET parentId = @p0
11097
+ OUTPUT INSERTED.id
11098
+ WHERE id = @p1`,
11099
+ [parentId, messageId]
11100
+ );
11101
+ if (rows.length === 0) {
11102
+ throw new Error(`Message ${messageId} not found`);
11103
+ }
11104
+ }
10996
11105
  async getMessage(messageId) {
10997
11106
  const rows = await this.#query(`SELECT * FROM ${this.#t("messages")} WHERE id = @p0`, [messageId]);
10998
11107
  if (rows.length === 0) {
@@ -11118,11 +11227,15 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
11118
11227
  );
11119
11228
  });
11120
11229
  }
11121
- async updateBranchHead(branchId, messageId) {
11122
- await this.#query(
11123
- `UPDATE ${this.#t("branches")} SET headMessageId = @p0 WHERE id = @p1`,
11124
- [messageId, branchId]
11230
+ async updateBranchHead(branchId, messageId, expectedHeadMessageId) {
11231
+ const rows = await this.#query(
11232
+ `UPDATE ${this.#t("branches")} SET headMessageId = @p0
11233
+ OUTPUT INSERTED.id
11234
+ WHERE id = @p1
11235
+ AND (headMessageId = @p2 OR (headMessageId IS NULL AND @p2 IS NULL))`,
11236
+ [messageId, branchId, expectedHeadMessageId]
11125
11237
  );
11238
+ return rows.length > 0;
11126
11239
  }
11127
11240
  async listBranches(chatId) {
11128
11241
  const branches = await this.#query(
@@ -12974,6 +13087,7 @@ export {
12974
13087
  FunctionResolver,
12975
13088
  GeneratorResolver,
12976
13089
  GithubReleaseInstaller,
13090
+ HeadConflictError,
12977
13091
  InMemoryContextStore,
12978
13092
  InstallError,
12979
13093
  Installer,