@deepagents/context 7.1.0 → 7.1.2

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
@@ -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++;
2734
- }
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
- };
2722
+ if (isUIMessage(encoded) && isSyntheticReminderMessage(encoded)) continue;
2723
+ turn++;
2746
2724
  }
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);
@@ -4101,6 +4077,25 @@ function mergeTerminalToolParts(message2, liveParts) {
4101
4077
  parts.push(...remaining.values());
4102
4078
  return { ...message2, parts };
4103
4079
  }
4080
+ var UNOPENED = {
4081
+ chat: null,
4082
+ branchName: "main",
4083
+ headMessageId: void 0
4084
+ };
4085
+ var OpenChat = class {
4086
+ chat;
4087
+ branch;
4088
+ constructor(chat2, branch) {
4089
+ this.chat = chat2;
4090
+ this.branch = branch;
4091
+ }
4092
+ get branchName() {
4093
+ return this.branch.name;
4094
+ }
4095
+ get headMessageId() {
4096
+ return this.branch.headMessageId ?? void 0;
4097
+ }
4098
+ };
4104
4099
  var ContextEngine = class _ContextEngine {
4105
4100
  /** Non-message fragments (role, hints, etc.) - not persisted in graph */
4106
4101
  #fragments = [];
@@ -4109,23 +4104,13 @@ var ContextEngine = class _ContextEngine {
4109
4104
  #store;
4110
4105
  #chatId;
4111
4106
  #userId;
4112
- #branchName;
4113
- #branch = null;
4114
- #chatData = null;
4115
- #initialized = false;
4116
- /** Initial metadata to merge on first initialization */
4117
- #initialMetadata;
4107
+ /** What this engine knows of its chat: nothing until the first store call opens it. */
4108
+ #session = UNOPENED;
4109
+ /** Opens the chat once; concurrent first calls share it, and a failed open runs again. */
4110
+ #open;
4118
4111
  #loaderResolver;
4119
4112
  /** Retained so forked children inherit the parent's resolver chain */
4120
4113
  #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
4114
  get #renderableFragments() {
4130
4115
  return this.#fragments.filter((f) => !isConditionalReminder(f));
4131
4116
  }
@@ -4139,38 +4124,30 @@ var ContextEngine = class _ContextEngine {
4139
4124
  this.#store = options.store;
4140
4125
  this.#chatId = options.chatId;
4141
4126
  this.#userId = options.userId;
4142
- this.#branchName = "main";
4143
- this.#initialMetadata = options.metadata;
4127
+ this.#open = pMemoize2(() => this.#openChat(options.metadata));
4144
4128
  this.#resolvers = options.resolvers;
4145
4129
  this.#loaderResolver = new FragmentLoaderResolver(
4146
4130
  options.resolvers ?? defaultResolvers()
4147
4131
  );
4148
4132
  }
4149
4133
  /**
4150
- * Initialize the chat and branch if they don't exist.
4134
+ * Creates the chat if it is new, checks its owner, merges the initial
4135
+ * metadata, and reads the active branch.
4151
4136
  */
4152
- async #ensureInitialized() {
4153
- if (this.#initialized) {
4154
- return;
4155
- }
4156
- this.#chatData = await this.#store.upsertChat({
4137
+ async #openChat(metadata) {
4138
+ let chat2 = await this.#store.upsertChat({
4157
4139
  id: this.#chatId,
4158
4140
  userId: this.#userId
4159
4141
  });
4160
- if (this.#chatData.userId !== this.#userId) {
4142
+ if (chat2.userId !== this.#userId) {
4161
4143
  throw new Error(
4162
- `chat "${this.#chatId}" belongs to user "${this.#chatData.userId}", not "${this.#userId}"`
4144
+ `chat "${this.#chatId}" belongs to user "${chat2.userId}", not "${this.#userId}"`
4163
4145
  );
4164
4146
  }
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;
4147
+ if (metadata) {
4148
+ chat2 = await this.#store.updateChat(this.#chatId, (stored) => ({
4149
+ metadata: { ...stored.metadata, ...metadata }
4150
+ }));
4174
4151
  }
4175
4152
  const branch = await this.#store.getActiveBranch(this.#chatId);
4176
4153
  if (!branch) {
@@ -4178,20 +4155,21 @@ var ContextEngine = class _ContextEngine {
4178
4155
  `Active branch not found for chat "${this.#chatId}" after upsertChat`
4179
4156
  );
4180
4157
  }
4181
- this.#branch = branch;
4182
- this.#branchName = branch.name;
4183
- this.#initialized = true;
4158
+ const open = new OpenChat(chat2, branch);
4159
+ this.#session = open;
4160
+ return open;
4184
4161
  }
4185
4162
  /**
4186
4163
  * Create a new branch from a specific message.
4187
4164
  * Shared logic between rewind() and btw().
4188
4165
  */
4189
- async #createBranchFrom(messageId, switchTo) {
4166
+ async #createBranchFrom(open, messageId, switchTo) {
4190
4167
  const branches = await this.#store.listBranches(this.#chatId);
4168
+ const baseName = open.branch.name;
4191
4169
  const samePrefix = branches.filter(
4192
- (it) => it.name === this.#branchName || it.name.startsWith(`${this.#branchName}-v`)
4170
+ (it) => it.name === baseName || it.name.startsWith(`${baseName}-v`)
4193
4171
  );
4194
- const newBranchName = `${this.#branchName}-v${samePrefix.length + 1}`;
4172
+ const newBranchName = `${baseName}-v${samePrefix.length + 1}`;
4195
4173
  const newBranch = {
4196
4174
  id: crypto.randomUUID(),
4197
4175
  chatId: this.#chatId,
@@ -4203,8 +4181,7 @@ var ContextEngine = class _ContextEngine {
4203
4181
  await this.#store.createBranch(newBranch);
4204
4182
  if (switchTo) {
4205
4183
  await this.#store.setActiveBranch(this.#chatId, newBranch.id);
4206
- this.#branch = { ...newBranch, isActive: true };
4207
- this.#branchName = newBranchName;
4184
+ open.branch = { ...newBranch, isActive: true };
4208
4185
  this.#pendingMessages = [];
4209
4186
  }
4210
4187
  const chain = await this.#store.getMessageChain(messageId);
@@ -4236,47 +4213,40 @@ var ContextEngine = class _ContextEngine {
4236
4213
  * Get the current branch name.
4237
4214
  */
4238
4215
  get branch() {
4239
- return this.#branchName;
4216
+ return this.#session.branchName;
4240
4217
  }
4241
4218
  /**
4242
4219
  * Get the current branch head message ID.
4243
4220
  * Returns undefined if no messages have been saved yet.
4244
4221
  */
4245
4222
  get headMessageId() {
4246
- return this.#branch?.headMessageId ?? void 0;
4223
+ return this.#session.headMessageId;
4247
4224
  }
4248
4225
  /**
4249
4226
  * Get metadata for the current chat.
4250
4227
  * Returns null if the chat hasn't been initialized yet.
4251
4228
  */
4252
4229
  get chat() {
4253
- return this.#chatData;
4230
+ return this.#session.chat;
4254
4231
  }
4255
4232
  /**
4256
4233
  * Count user turns in the conversation and return the previous saved user message context.
4257
4234
  * Includes persisted messages and pending messages in the turn count.
4258
4235
  */
4259
4236
  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();
4237
+ const open = await this.#open();
4238
+ const stored = open.branch.headMessageId ? await this.#store.getMessageChain(open.branch.headMessageId) : [];
4239
+ return summarizeChain(stored, pending);
4270
4240
  }
4271
4241
  async getTurnCount() {
4272
4242
  const { turn } = await this.#getChainContext();
4273
4243
  return turn;
4274
4244
  }
4275
4245
  async firstUserMessage() {
4276
- await this.#ensureInitialized();
4277
- if (this.#branch?.headMessageId) {
4246
+ const open = await this.#open();
4247
+ if (open.branch.headMessageId) {
4278
4248
  const chain = await this.#store.getMessageChain(
4279
- this.#branch.headMessageId
4249
+ open.branch.headMessageId
4280
4250
  );
4281
4251
  for (const msg of chain) {
4282
4252
  if (msg.name === "user") {
@@ -4305,7 +4275,7 @@ var ContextEngine = class _ContextEngine {
4305
4275
  * @throws if the pending tail is missing an id (programming error).
4306
4276
  */
4307
4277
  async headMessage() {
4308
- await this.#ensureInitialized();
4278
+ const open = await this.#open();
4309
4279
  if (this.#pendingMessages.length > 0) {
4310
4280
  const tail = this.#pendingMessages[this.#pendingMessages.length - 1];
4311
4281
  if (!tail.id) {
@@ -4315,8 +4285,8 @@ var ContextEngine = class _ContextEngine {
4315
4285
  }
4316
4286
  return { id: tail.id, name: tail.name };
4317
4287
  }
4318
- if (this.#branch?.headMessageId) {
4319
- const msg = await this.#store.getMessage(this.#branch.headMessageId);
4288
+ if (open.branch.headMessageId) {
4289
+ const msg = await this.#store.getMessage(open.branch.headMessageId);
4320
4290
  if (msg) return { id: msg.id, name: msg.name };
4321
4291
  }
4322
4292
  return void 0;
@@ -4336,12 +4306,12 @@ var ContextEngine = class _ContextEngine {
4336
4306
  * For id-lookup use `headMessage()` instead — that one keeps placeholders.
4337
4307
  */
4338
4308
  async getMessages() {
4339
- await this.#ensureInitialized();
4309
+ const open = await this.#open();
4340
4310
  const messages = [];
4341
4311
  const indexById = /* @__PURE__ */ new Map();
4342
- if (this.#branch?.headMessageId) {
4312
+ if (open.branch.headMessageId) {
4343
4313
  const chain = await this.#store.getMessageChain(
4344
- this.#branch.headMessageId
4314
+ open.branch.headMessageId
4345
4315
  );
4346
4316
  for (const msg of chain) {
4347
4317
  const data = requireUIMessage(msg.data, `Stored message "${msg.id}"`);
@@ -4452,7 +4422,7 @@ var ContextEngine = class _ContextEngine {
4452
4422
  * ```
4453
4423
  */
4454
4424
  async resolve(options) {
4455
- await this.#ensureInitialized();
4425
+ await this.#open();
4456
4426
  await this.#loaderResolver.resolve(this.#fragments, {
4457
4427
  sandbox: options.sandbox,
4458
4428
  context: this,
@@ -4477,15 +4447,19 @@ var ContextEngine = class _ContextEngine {
4477
4447
  * ```
4478
4448
  */
4479
4449
  async save(options) {
4480
- await this.#ensureInitialized();
4450
+ const open = await this.#open();
4481
4451
  if (this.#pendingMessages.length === 0) {
4482
- return { headMessageId: this.#branch?.headMessageId ?? void 0 };
4452
+ return { headMessageId: open.branch.headMessageId ?? void 0 };
4483
4453
  }
4484
4454
  const pending = this.#pendingMessages;
4485
4455
  this.#pendingMessages = [];
4486
- const pipeline = new SavePipeline(this.#asSavePipelineEngine(), pending);
4456
+ const pipeline = new SavePipeline(
4457
+ this.#asSavePipelineEngine(open),
4458
+ pending,
4459
+ options?.branch ?? true
4460
+ );
4487
4461
  try {
4488
- await pipeline.applyUpdateBranching(options?.branch ?? true);
4462
+ await pipeline.applyUpdateBranching();
4489
4463
  await this.#foldUserReminders(pending);
4490
4464
  return await pipeline.persist();
4491
4465
  } catch (error) {
@@ -4726,13 +4700,14 @@ var ContextEngine = class _ContextEngine {
4726
4700
  return this.#fragments.filter(isConditionalReminder).map((fragment2) => fragment2.metadata.reminder).filter((config) => config.target === target);
4727
4701
  }
4728
4702
  #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) {
4703
+ const session = this.#session;
4704
+ if (!(session instanceof OpenChat)) {
4734
4705
  throw new Error("ContextEngine must be initialized before reminders run");
4735
4706
  }
4707
+ const chatData = session.chat;
4708
+ const rawUsage = chatData.metadata?.usage;
4709
+ const usage = isLanguageModelUsage(rawUsage) ? rawUsage : void 0;
4710
+ const elapsed = chain.lastMessageAt !== void 0 ? Date.now() - chain.lastMessageAt : void 0;
4736
4711
  return {
4737
4712
  turn: chain.turn,
4738
4713
  messageCount: chain.messageCount,
@@ -4740,7 +4715,7 @@ var ContextEngine = class _ContextEngine {
4740
4715
  lastMessage: chain.lastMessage,
4741
4716
  chat: chatData,
4742
4717
  usage,
4743
- branch: this.#branchName,
4718
+ branch: session.branchName,
4744
4719
  elapsed
4745
4720
  };
4746
4721
  }
@@ -4861,36 +4836,36 @@ var ContextEngine = class _ContextEngine {
4861
4836
  }
4862
4837
  return { texts, onceIds: [...onceIds] };
4863
4838
  }
4864
- #asSavePipelineEngine() {
4839
+ #asSavePipelineEngine(open) {
4865
4840
  return {
4866
4841
  store: this.#store,
4867
4842
  chatId: this.#chatId,
4868
4843
  getActiveBranch: () => ({
4869
- id: this.#activeBranch.id,
4870
- headMessageId: this.#activeBranch.headMessageId
4844
+ id: open.branch.id,
4845
+ headMessageId: open.branch.headMessageId
4871
4846
  }),
4872
4847
  commitHead: async (headMessageId, expectedHeadMessageId) => {
4873
4848
  const committed = await this.#store.updateBranchHead(
4874
- this.#activeBranch.id,
4849
+ open.branch.id,
4875
4850
  headMessageId,
4876
4851
  expectedHeadMessageId
4877
4852
  );
4878
4853
  if (committed) {
4879
- this.#activeBranch.headMessageId = headMessageId;
4854
+ open.branch.headMessageId = headMessageId;
4880
4855
  }
4881
4856
  return committed;
4882
4857
  },
4883
4858
  refreshBranch: async () => {
4884
4859
  const fresh = await this.#store.getBranch(
4885
4860
  this.#chatId,
4886
- this.#activeBranch.name
4861
+ open.branch.name
4887
4862
  );
4888
4863
  if (!fresh) {
4889
4864
  throw new Error(
4890
- `Branch "${this.#activeBranch.name}" not found for chat "${this.#chatId}"`
4865
+ `Branch "${open.branch.name}" not found for chat "${this.#chatId}"`
4891
4866
  );
4892
4867
  }
4893
- this.#branch = fresh;
4868
+ open.branch = fresh;
4894
4869
  return { id: fresh.id, headMessageId: fresh.headMessageId };
4895
4870
  },
4896
4871
  rewindForUpdate: (parentId) => this.#rewindForUpdate(parentId)
@@ -4909,7 +4884,7 @@ var ContextEngine = class _ContextEngine {
4909
4884
  * @returns Estimate result with token counts, costs, and per-fragment breakdown
4910
4885
  */
4911
4886
  async estimate(modelId, options = {}) {
4912
- await this.#ensureInitialized();
4887
+ const open = await this.#open();
4913
4888
  await this.#loaderResolver.resolve(this.#fragments, {
4914
4889
  sandbox: options.sandbox,
4915
4890
  context: this,
@@ -4938,9 +4913,9 @@ var ContextEngine = class _ContextEngine {
4938
4913
  cost
4939
4914
  });
4940
4915
  }
4941
- if (this.#branch?.headMessageId) {
4916
+ if (open.branch.headMessageId) {
4942
4917
  const chain = await this.#store.getMessageChain(
4943
- this.#branch.headMessageId
4918
+ open.branch.headMessageId
4944
4919
  );
4945
4920
  for (const msg of chain) {
4946
4921
  const content = estimateMessageContent(msg.data);
@@ -5012,7 +4987,7 @@ var ContextEngine = class _ContextEngine {
5012
4987
  * ```
5013
4988
  */
5014
4989
  async rewind(messageId) {
5015
- await this.#ensureInitialized();
4990
+ const open = await this.#open();
5016
4991
  const message2 = await this.#store.getMessage(messageId);
5017
4992
  if (!message2) {
5018
4993
  throw new Error(`Message "${messageId}" not found`);
@@ -5020,7 +4995,7 @@ var ContextEngine = class _ContextEngine {
5020
4995
  if (message2.chatId !== this.#chatId) {
5021
4996
  throw new Error(`Message "${messageId}" belongs to a different chat`);
5022
4997
  }
5023
- return this.#createBranchFrom(messageId, true);
4998
+ return this.#createBranchFrom(open, messageId, true);
5024
4999
  }
5025
5000
  /**
5026
5001
  * Create a checkpoint at the current position.
@@ -5042,15 +5017,15 @@ var ContextEngine = class _ContextEngine {
5042
5017
  * ```
5043
5018
  */
5044
5019
  async checkpoint(name) {
5045
- await this.#ensureInitialized();
5046
- if (!this.#branch?.headMessageId) {
5020
+ const open = await this.#open();
5021
+ if (!open.branch.headMessageId) {
5047
5022
  throw new Error("Cannot create checkpoint: no messages in conversation");
5048
5023
  }
5049
5024
  const checkpoint = {
5050
5025
  id: crypto.randomUUID(),
5051
5026
  chatId: this.#chatId,
5052
5027
  name,
5053
- messageId: this.#branch.headMessageId,
5028
+ messageId: open.branch.headMessageId,
5054
5029
  createdAt: Date.now()
5055
5030
  };
5056
5031
  await this.#store.createCheckpoint(checkpoint);
@@ -5078,7 +5053,7 @@ var ContextEngine = class _ContextEngine {
5078
5053
  * ```
5079
5054
  */
5080
5055
  async restore(name) {
5081
- await this.#ensureInitialized();
5056
+ await this.#open();
5082
5057
  const checkpoint = await this.#store.getCheckpoint(this.#chatId, name);
5083
5058
  if (!checkpoint) {
5084
5059
  throw new Error(
@@ -5103,14 +5078,13 @@ var ContextEngine = class _ContextEngine {
5103
5078
  * ```
5104
5079
  */
5105
5080
  async switchBranch(name) {
5106
- await this.#ensureInitialized();
5081
+ const open = await this.#open();
5107
5082
  const branch = await this.#store.getBranch(this.#chatId, name);
5108
5083
  if (!branch) {
5109
5084
  throw new Error(`Branch "${name}" not found in chat "${this.#chatId}"`);
5110
5085
  }
5111
5086
  await this.#store.setActiveBranch(this.#chatId, branch.id);
5112
- this.#branch = { ...branch, isActive: true };
5113
- this.#branchName = name;
5087
+ open.branch = { ...branch, isActive: true };
5114
5088
  this.#pendingMessages = [];
5115
5089
  }
5116
5090
  /**
@@ -5145,11 +5119,11 @@ var ContextEngine = class _ContextEngine {
5145
5119
  * ```
5146
5120
  */
5147
5121
  async btw() {
5148
- await this.#ensureInitialized();
5149
- if (!this.#branch?.headMessageId) {
5122
+ const open = await this.#open();
5123
+ if (!open.branch.headMessageId) {
5150
5124
  throw new Error("Cannot create btw branch: no messages in conversation");
5151
5125
  }
5152
- return this.#createBranchFrom(this.#branch.headMessageId, false);
5126
+ return this.#createBranchFrom(open, open.branch.headMessageId, false);
5153
5127
  }
5154
5128
  /**
5155
5129
  * Update metadata for the current chat.
@@ -5165,14 +5139,11 @@ var ContextEngine = class _ContextEngine {
5165
5139
  * ```
5166
5140
  */
5167
5141
  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
- );
5142
+ const open = await this.#open();
5143
+ open.chat = await this.#store.updateChat(this.#chatId, ({ metadata }) => ({
5144
+ title: updates.title,
5145
+ metadata: updates.metadata === void 0 ? void 0 : { ...metadata, ...updates.metadata }
5146
+ }));
5176
5147
  }
5177
5148
  /**
5178
5149
  * Track token usage for the current chat.
@@ -5188,20 +5159,17 @@ var ContextEngine = class _ContextEngine {
5188
5159
  * ```
5189
5160
  */
5190
5161
  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
- );
5162
+ const open = await this.#open();
5163
+ open.chat = await this.#store.updateChat(this.#chatId, ({ metadata }) => {
5164
+ const storedUsage = metadata?.usage;
5165
+ const currentUsage = isLanguageModelUsage(storedUsage) ? storedUsage : void 0;
5166
+ return {
5167
+ metadata: {
5168
+ ...metadata,
5169
+ usage: mergeLanguageModelUsage(currentUsage, usage)
5170
+ }
5171
+ };
5172
+ });
5205
5173
  }
5206
5174
  /**
5207
5175
  * Consolidate context fragments (no-op for now).
@@ -5267,7 +5235,7 @@ var ContextEngine = class _ContextEngine {
5267
5235
  * ```
5268
5236
  */
5269
5237
  async inspect(options) {
5270
- await this.#ensureInitialized();
5238
+ const open = await this.#open();
5271
5239
  const { renderer } = options;
5272
5240
  const estimateResult = await this.estimate(options.modelId, {
5273
5241
  renderer,
@@ -5276,9 +5244,9 @@ var ContextEngine = class _ContextEngine {
5276
5244
  });
5277
5245
  const rendered = renderer.render(this.#renderableFragments);
5278
5246
  const persistedMessages = [];
5279
- if (this.#branch?.headMessageId) {
5247
+ if (open.branch.headMessageId) {
5280
5248
  const chain = await this.#store.getMessageChain(
5281
- this.#branch.headMessageId
5249
+ open.branch.headMessageId
5282
5250
  );
5283
5251
  persistedMessages.push(...chain);
5284
5252
  }
@@ -5294,7 +5262,7 @@ var ContextEngine = class _ContextEngine {
5294
5262
  graph,
5295
5263
  meta: {
5296
5264
  chatId: this.#chatId,
5297
- branch: this.#branchName,
5265
+ branch: open.branch.name,
5298
5266
  timestamp: Date.now()
5299
5267
  }
5300
5268
  };
@@ -10740,7 +10708,7 @@ async function assertMicrosandboxBash(vm) {
10740
10708
  function normalizeMicrosandboxError(error, sdk) {
10741
10709
  const err = toError2(error);
10742
10710
  if (err instanceof MicrosandboxSandboxError) return err;
10743
- if (err instanceof sdk.LibkrunfwNotFoundError) {
10711
+ if (err instanceof sdk.LibkrunfwNotFoundError || err instanceof sdk.RuntimeNotInstalledError || err instanceof sdk.RuntimeIncompleteError) {
10744
10712
  return new MicrosandboxNotAvailableError(err);
10745
10713
  }
10746
10714
  if (err instanceof sdk.MicrosandboxError) {
@@ -11458,7 +11426,6 @@ var PostgresContextStore = class _PostgresContextStore extends ContextStore {
11458
11426
  #pool;
11459
11427
  #schema;
11460
11428
  #ownsPool;
11461
- #isInitialized = false;
11462
11429
  constructor(options) {
11463
11430
  super();
11464
11431
  const schema = options.schema ?? "public";
@@ -11488,24 +11455,26 @@ var PostgresContextStore = class _PostgresContextStore extends ContextStore {
11488
11455
  #t(name) {
11489
11456
  return `"${this.#schema}"."${name}"`;
11490
11457
  }
11491
- async initialize() {
11458
+ /** Opens the store: creates its schema objects before it returns. */
11459
+ static async create(options) {
11460
+ const store = new _PostgresContextStore(options);
11461
+ try {
11462
+ await store.#initialize();
11463
+ } catch (error) {
11464
+ await store.close();
11465
+ throw error;
11466
+ }
11467
+ return store;
11468
+ }
11469
+ async #initialize() {
11492
11470
  const ddl = storeDDL(this.#schema);
11493
11471
  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
- );
11501
- }
11502
11472
  }
11503
11473
  /**
11504
11474
  * Execute a function within a transaction.
11505
11475
  * Automatically commits on success or rolls back on error.
11506
11476
  */
11507
11477
  async #useTransaction(fn) {
11508
- this.#ensureInitialized();
11509
11478
  const client = await this.#pool.connect();
11510
11479
  try {
11511
11480
  await client.query("BEGIN");
@@ -11523,7 +11492,6 @@ var PostgresContextStore = class _PostgresContextStore extends ContextStore {
11523
11492
  * Execute a query using the pool (no transaction).
11524
11493
  */
11525
11494
  async #query(sql, params) {
11526
- this.#ensureInitialized();
11527
11495
  const result = await this.#pool.query(sql, params);
11528
11496
  return result.rows;
11529
11497
  }
@@ -12072,6 +12040,11 @@ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${schema}_branches_ch
12072
12040
  IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${schema}_branches_chatId_isActive' AND object_id = OBJECT_ID('[${schema}].[branches]'))
12073
12041
  CREATE INDEX [idx_${schema}_branches_chatId_isActive] ON [${schema}].[branches](chatId, isActive);
12074
12042
 
12043
+ -- Deleting a message checks this foreign key. Without the index the check
12044
+ -- scans every chat's branches and waits on rows another chat delete holds.
12045
+ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'idx_${schema}_branches_headMessageId' AND object_id = OBJECT_ID('[${schema}].[branches]'))
12046
+ CREATE INDEX [idx_${schema}_branches_headMessageId] ON [${schema}].[branches](headMessageId);
12047
+
12075
12048
  IF OBJECT_ID('[${schema}].[checkpoints]', 'U') IS NULL
12076
12049
  BEGIN
12077
12050
  CREATE TABLE [${schema}].[checkpoints] (
@@ -12225,7 +12198,6 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12225
12198
  #pool;
12226
12199
  #schema;
12227
12200
  #ownsPool;
12228
- #isInitialized = false;
12229
12201
  constructor(options) {
12230
12202
  super();
12231
12203
  const schema = options.schema ?? "dbo";
@@ -12255,7 +12227,18 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12255
12227
  #t(name) {
12256
12228
  return `[${this.#schema}].[${name}]`;
12257
12229
  }
12258
- async initialize() {
12230
+ /** Opens the store: connects a pool it owns and creates its schema objects before it returns. */
12231
+ static async create(options) {
12232
+ const store = new _SqlServerContextStore(options);
12233
+ try {
12234
+ await store.#initialize();
12235
+ } catch (error) {
12236
+ await store.close();
12237
+ throw error;
12238
+ }
12239
+ return store;
12240
+ }
12241
+ async #initialize() {
12259
12242
  if (this.#ownsPool) {
12260
12243
  await this.#pool.connect();
12261
12244
  }
@@ -12275,21 +12258,12 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12275
12258
  await this.#pool.request().batch(batch);
12276
12259
  }
12277
12260
  }
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
- }
12286
12261
  }
12287
12262
  /**
12288
12263
  * Execute a function within a transaction.
12289
12264
  * Automatically commits on success or rolls back on error.
12290
12265
  */
12291
12266
  async #useTransaction(fn) {
12292
- this.#ensureInitialized();
12293
12267
  const mssql = _SqlServerContextStore.#requireMssql();
12294
12268
  const transaction = new mssql.Transaction(this.#pool);
12295
12269
  try {
@@ -12307,7 +12281,6 @@ var SqlServerContextStore = class _SqlServerContextStore extends ContextStore {
12307
12281
  * Converts positional params to SQL Server named params (@p0, @p1, ...).
12308
12282
  */
12309
12283
  async #query(sql, params) {
12310
- this.#ensureInitialized();
12311
12284
  const request = this.#pool.request();
12312
12285
  params?.forEach((value, index) => {
12313
12286
  request.input(`p${index}`, value);
@@ -13159,7 +13132,9 @@ function staticPartText(part, options) {
13159
13132
  }
13160
13133
 
13161
13134
  // packages/context/src/lib/stream/postgres-notify-change-source.ts
13135
+ import { EventEmitter, on } from "node:events";
13162
13136
  import { createRequire as createRequire3 } from "node:module";
13137
+ import pMemoize3 from "p-memoize";
13163
13138
  import { z as z16 } from "zod";
13164
13139
 
13165
13140
  // packages/context/src/lib/stream/ddl.stream.postgres-notify.ts
@@ -13220,28 +13195,73 @@ EXECUTE FUNCTION "${schema}"."notify_stream_status_update"();
13220
13195
  }
13221
13196
 
13222
13197
  // packages/context/src/lib/stream/postgres-notify-change-source.ts
13198
+ var NOT_LISTENING = {
13199
+ release: () => Promise.resolve(),
13200
+ fail: () => {
13201
+ }
13202
+ };
13203
+ var Listening = class {
13204
+ #client;
13205
+ #handlers;
13206
+ constructor(client, handlers) {
13207
+ this.#client = client;
13208
+ this.#handlers = handlers;
13209
+ client.on("notification", handlers.notification);
13210
+ client.on("error", handlers.error);
13211
+ client.on("end", handlers.end);
13212
+ }
13213
+ /** Stops listening and returns the connection to the pool. */
13214
+ async release(channel) {
13215
+ this.#detach();
13216
+ try {
13217
+ await this.#client.query(`UNLISTEN ${quoteIdentifier(channel)}`);
13218
+ } finally {
13219
+ this.#client.release();
13220
+ }
13221
+ }
13222
+ /** Gives a broken connection back for the pool to destroy. */
13223
+ fail(error) {
13224
+ this.#detach();
13225
+ this.#client.release(error);
13226
+ }
13227
+ #detach() {
13228
+ this.#client.off("notification", this.#handlers.notification);
13229
+ this.#client.off("error", this.#handlers.error);
13230
+ this.#client.off("end", this.#handlers.end);
13231
+ }
13232
+ };
13233
+ var ChangeDelivery = class {
13234
+ change;
13235
+ constructor(change) {
13236
+ this.change = change;
13237
+ }
13238
+ };
13223
13239
  var PostgresNotifyChangeSource = class _PostgresNotifyChangeSource {
13224
13240
  #pool;
13225
13241
  #schema;
13226
13242
  #channel;
13227
13243
  #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
- );
13244
+ /** Aborts when close() starts: every subscription ends, and no LISTEN connection opens. */
13245
+ #closed = new AbortController();
13246
+ /** Emits `stream:<id>` with a ChangeDelivery; `error` fails every subscription. */
13247
+ #changes = new EventEmitter().setMaxListeners(0);
13248
+ #subscriptions = 0;
13249
+ #listener = NOT_LISTENING;
13250
+ /** Concurrent subscribers share one LISTEN setup; a failed one runs again. */
13251
+ #listen = pMemoize3(() => this.#openListener(), { cache: false });
13252
+ #handlers = {
13253
+ notification: (message2) => {
13254
+ this.#handleNotification(message2);
13255
+ },
13256
+ error: (error) => {
13257
+ this.#failListener(error);
13258
+ },
13259
+ end: () => {
13260
+ if (!this.#closed.signal.aborted) {
13261
+ this.#failListener(
13262
+ new Error("PostgreSQL stream notification listener ended")
13263
+ );
13264
+ }
13245
13265
  }
13246
13266
  };
13247
13267
  constructor(options) {
@@ -13270,98 +13290,75 @@ var PostgresNotifyChangeSource = class _PostgresNotifyChangeSource {
13270
13290
  );
13271
13291
  }
13272
13292
  }
13273
- async initialize() {
13274
- await this.#pool.query(
13275
- postgresStreamNotifyDDL(this.#schema, this.#channel)
13276
- );
13277
- this.#isInitialized = true;
13293
+ /** Opens the source: installs the notify triggers before it returns. */
13294
+ static async create(options) {
13295
+ const source = new _PostgresNotifyChangeSource(options);
13296
+ try {
13297
+ await source.#pool.query(
13298
+ postgresStreamNotifyDDL(source.#schema, source.#channel)
13299
+ );
13300
+ } catch (error) {
13301
+ await source.close();
13302
+ throw error;
13303
+ }
13304
+ return source;
13278
13305
  }
13279
13306
  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 });
13307
+ if (this.#closed.signal.aborted) {
13308
+ throw new Error("PostgresNotifyChangeSource is closed.");
13309
+ }
13310
+ if (signal.aborted) return;
13311
+ const changes = on(this.#changes, `stream:${streamId}`, {
13312
+ signal: AbortSignal.any([signal, this.#closed.signal])
13313
+ });
13314
+ this.#subscriptions += 1;
13289
13315
  try {
13290
13316
  await this.#ensureListening();
13291
- if (signal.aborted || this.#isClosed) return;
13317
+ if (signal.aborted || this.#closed.signal.aborted) return;
13292
13318
  yield { kind: "tick" };
13293
- while (!signal.aborted && !this.#isClosed) {
13294
- const change = await queue.next(signal);
13295
- if (!change) return;
13296
- yield change;
13319
+ for await (const [delivery] of changes) {
13320
+ if (delivery instanceof ChangeDelivery) yield delivery.change;
13297
13321
  }
13322
+ } catch (error) {
13323
+ if (error instanceof Error && error.name === "AbortError") return;
13324
+ throw error;
13298
13325
  } finally {
13299
- signal.removeEventListener("abort", cleanup);
13300
- cleanup();
13326
+ this.#subscriptions -= 1;
13327
+ await changes.return?.();
13301
13328
  await this.#releaseListenerIfIdle();
13302
13329
  }
13303
13330
  }
13304
13331
  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
- }
13332
+ if (this.#closed.signal.aborted) return;
13333
+ this.#closed.abort();
13334
+ try {
13335
+ await this.#listen();
13336
+ } catch {
13319
13337
  }
13320
13338
  await this.#releaseListener();
13321
13339
  if (this.#ownsPool) {
13322
13340
  await this.#pool.end();
13323
13341
  }
13324
13342
  }
13325
- #ensureInitialized() {
13326
- if (!this.#isInitialized) {
13327
- throw new Error(
13328
- "PostgresNotifyChangeSource not initialized. Call await source.initialize() after construction."
13329
- );
13330
- }
13343
+ async #ensureListening() {
13344
+ if (this.#listener instanceof Listening) return;
13345
+ await this.#listen();
13331
13346
  }
13332
- #ensureOpen() {
13333
- if (this.#isClosed) {
13334
- throw new Error("PostgresNotifyChangeSource is closed.");
13347
+ async #openListener() {
13348
+ if (this.#closed.signal.aborted) return;
13349
+ const client = await this.#pool.connect();
13350
+ const listening = new Listening(client, this.#handlers);
13351
+ try {
13352
+ await client.query(`LISTEN ${quoteIdentifier(this.#channel)}`);
13353
+ } catch (error) {
13354
+ listening.fail(true);
13355
+ throw error;
13335
13356
  }
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;
13357
+ if (this.#closed.signal.aborted) {
13358
+ listening.fail(true);
13359
+ return;
13360
+ }
13361
+ this.#listener = listening;
13365
13362
  }
13366
13363
  #handleNotification(message2) {
13367
13364
  if (message2.channel !== this.#channel || !message2.payload) return;
@@ -13369,125 +13366,28 @@ var PostgresNotifyChangeSource = class _PostgresNotifyChangeSource {
13369
13366
  if (!payload) return;
13370
13367
  if (payload.schema !== this.#schema) return;
13371
13368
  if (payload.kind !== "chunks" && payload.kind !== "status") return;
13372
- const subscribers = this.#subscribers.get(payload.streamId);
13373
- if (!subscribers) return;
13374
13369
  const change = payload.kind === "chunks" ? { kind: "chunks" } : { kind: "status" };
13375
- for (const subscriber of subscribers) {
13376
- subscriber.push(change);
13377
- }
13370
+ this.#changes.emit(
13371
+ `stream:${payload.streamId}`,
13372
+ new ChangeDelivery(change)
13373
+ );
13378
13374
  }
13379
13375
  #failListener(error) {
13380
13376
  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
- }
13377
+ this.#listener = NOT_LISTENING;
13378
+ listener.fail(error);
13379
+ if (this.#changes.listenerCount("error") > 0) {
13380
+ this.#changes.emit("error", error);
13393
13381
  }
13394
- this.#subscribers.clear();
13395
13382
  }
13396
13383
  async #releaseListenerIfIdle() {
13397
- if (this.#subscribers.size > 0) return;
13384
+ if (this.#subscriptions > 0) return;
13398
13385
  await this.#releaseListener();
13399
13386
  }
13400
13387
  async #releaseListener() {
13401
13388
  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
- });
13389
+ this.#listener = NOT_LISTENING;
13390
+ await listener.release(this.#channel);
13491
13391
  }
13492
13392
  };
13493
13393
  var notificationPayload = z16.object({
@@ -13610,8 +13510,6 @@ var PostgresStreamStore = class _PostgresStreamStore extends StreamStore {
13610
13510
  #pool;
13611
13511
  #schema;
13612
13512
  #ownsPool;
13613
- #isInitialized = false;
13614
- #isClosed = false;
13615
13513
  constructor(options) {
13616
13514
  super();
13617
13515
  const schema = options.schema ?? "public";
@@ -13639,26 +13537,26 @@ var PostgresStreamStore = class _PostgresStreamStore extends StreamStore {
13639
13537
  #t(name) {
13640
13538
  return `"${this.#schema}"."${name}"`;
13641
13539
  }
13642
- async initialize() {
13540
+ /** Opens the store: creates its tables before it returns. */
13541
+ static async create(options) {
13542
+ const store = new _PostgresStreamStore(options);
13543
+ try {
13544
+ await store.#initialize();
13545
+ } catch (error) {
13546
+ await store.close();
13547
+ throw error;
13548
+ }
13549
+ return store;
13550
+ }
13551
+ async #initialize() {
13643
13552
  await this.#pool.query(postgresStreamDDL(this.#schema));
13644
- this.#isInitialized = true;
13645
13553
  }
13646
13554
  async close() {
13647
- if (this.#isClosed) return;
13648
- this.#isClosed = true;
13649
- if (this.#ownsPool) {
13555
+ if (this.#ownsPool && !this.#pool.ending) {
13650
13556
  await this.#pool.end();
13651
13557
  }
13652
13558
  }
13653
- #ensureInitialized() {
13654
- if (!this.#isInitialized) {
13655
- throw new Error(
13656
- "PostgresStreamStore not initialized. Call await store.initialize() after construction."
13657
- );
13658
- }
13659
- }
13660
13559
  async #query(sql, params) {
13661
- this.#ensureInitialized();
13662
13560
  const result = await this.#pool.query(sql, params);
13663
13561
  return result.rows;
13664
13562
  }
@@ -13666,7 +13564,6 @@ var PostgresStreamStore = class _PostgresStreamStore extends StreamStore {
13666
13564
  return z18.array(row).parse(await this.#query(sql, params));
13667
13565
  }
13668
13566
  async #useTransaction(fn) {
13669
- this.#ensureInitialized();
13670
13567
  const client = await this.#pool.connect();
13671
13568
  try {
13672
13569
  await client.query("BEGIN");
@@ -13949,7 +13846,7 @@ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
13949
13846
  import { z as z19 } from "zod";
13950
13847
 
13951
13848
  // packages/context/src/lib/stream/ddl.stream.sqlite.sql
13952
- var ddl_stream_sqlite_default = "PRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS streams (\n id TEXT PRIMARY KEY,\n status TEXT NOT NULL DEFAULT 'queued'\n CHECK(status IN ('queued','running','completed','failed','cancelled')),\n createdAt INTEGER NOT NULL,\n startedAt INTEGER,\n finishedAt INTEGER,\n cancelRequestedAt INTEGER,\n error TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stream_chunks (\n streamId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n data TEXT NOT NULL,\n createdAt INTEGER NOT NULL,\n PRIMARY KEY (streamId, seq),\n FOREIGN KEY (streamId) REFERENCES streams(id) ON DELETE CASCADE\n);\n\n-- Supports ordered listing across all streams.\nCREATE INDEX IF NOT EXISTS idx_streams_created_at_id\n ON streams(createdAt, id);\n\n-- Supports status-filtered ordered listing (e.g. running streams).\nCREATE INDEX IF NOT EXISTS idx_streams_status_created_at_id\n ON streams(status, createdAt, id);\n";
13849
+ var ddl_stream_sqlite_default = "PRAGMA busy_timeout = 5000;\nPRAGMA journal_mode = WAL;\nPRAGMA synchronous = NORMAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS streams (\n id TEXT PRIMARY KEY,\n status TEXT NOT NULL DEFAULT 'queued'\n CHECK(status IN ('queued','running','completed','failed','cancelled')),\n createdAt INTEGER NOT NULL,\n startedAt INTEGER,\n finishedAt INTEGER,\n cancelRequestedAt INTEGER,\n error TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stream_chunks (\n streamId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n data TEXT NOT NULL,\n createdAt INTEGER NOT NULL,\n PRIMARY KEY (streamId, seq),\n FOREIGN KEY (streamId) REFERENCES streams(id) ON DELETE CASCADE\n);\n\n-- Supports ordered listing across all streams.\nCREATE INDEX IF NOT EXISTS idx_streams_created_at_id\n ON streams(createdAt, id);\n\n-- Supports status-filtered ordered listing (e.g. running streams).\nCREATE INDEX IF NOT EXISTS idx_streams_status_created_at_id\n ON streams(status, createdAt, id);\n";
13953
13850
 
13954
13851
  // packages/context/src/lib/stream/sqlite.stream-store.ts
13955
13852
  var streamRow = z19.object({
@@ -13972,7 +13869,6 @@ var chunkRow2 = z19.object({
13972
13869
  var SqliteStreamStore = class extends StreamStore {
13973
13870
  #db;
13974
13871
  #statements = /* @__PURE__ */ new Map();
13975
- #closed = false;
13976
13872
  #stmt(sql) {
13977
13873
  let stmt = this.#statements.get(sql);
13978
13874
  if (!stmt) {
@@ -13982,8 +13878,7 @@ var SqliteStreamStore = class extends StreamStore {
13982
13878
  return stmt;
13983
13879
  }
13984
13880
  close() {
13985
- if (this.#closed) return;
13986
- this.#closed = true;
13881
+ if (!this.#db.isOpen) return;
13987
13882
  this.#statements.clear();
13988
13883
  this.#db.close();
13989
13884
  }
@@ -14124,8 +14019,7 @@ var SqliteStreamStore = class extends StreamStore {
14124
14019
  async appendChunks(chunks) {
14125
14020
  if (chunks.length === 0) return;
14126
14021
  const failures = collectStreamFailures(chunks);
14127
- this.#db.exec("BEGIN TRANSACTION");
14128
- try {
14022
+ this.#transaction(() => {
14129
14023
  for (const chunk of chunks) {
14130
14024
  this.#stmt(
14131
14025
  `INSERT INTO stream_chunks (streamId, seq, data, createdAt)
@@ -14152,11 +14046,7 @@ var SqliteStreamStore = class extends StreamStore {
14152
14046
  }
14153
14047
  }
14154
14048
  }
14155
- this.#db.exec("COMMIT");
14156
- } catch (error) {
14157
- this.#db.exec("ROLLBACK");
14158
- throw error;
14159
- }
14049
+ });
14160
14050
  }
14161
14051
  async getChunks(streamId, fromSeq, limit) {
14162
14052
  let sql = "SELECT * FROM stream_chunks WHERE streamId = ?";
@@ -14212,13 +14102,16 @@ var SqliteStreamStore = class extends StreamStore {
14212
14102
  });
14213
14103
  }
14214
14104
  #transaction(callback) {
14105
+ this.#db.exec("BEGIN IMMEDIATE");
14215
14106
  try {
14216
- this.#db.exec("BEGIN IMMEDIATE");
14217
14107
  const result = callback();
14218
14108
  this.#db.exec("COMMIT");
14219
14109
  return result;
14220
14110
  } catch (error) {
14221
- this.#db.exec("ROLLBACK");
14111
+ try {
14112
+ this.#db.exec("ROLLBACK");
14113
+ } catch {
14114
+ }
14222
14115
  throw error;
14223
14116
  }
14224
14117
  }
@@ -14686,7 +14579,6 @@ export {
14686
14579
  PostgresContextStore,
14687
14580
  PostgresNotifyChangeSource,
14688
14581
  PostgresStreamStore,
14689
- PromiseResolver,
14690
14582
  RuntimeStrategy,
14691
14583
  SqlServerContextStore,
14692
14584
  SqliteContextStore,