bunnyquery 1.11.1 → 1.12.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/engine.cjs CHANGED
@@ -627,6 +627,11 @@ function getErrorMessage(input) {
627
627
  }
628
628
  return "Something went wrong.";
629
629
  }
630
+ function isRetriedFailure(input) {
631
+ var envErr = csrEnvelopeError(input);
632
+ if (envErr !== void 0) input = envErr;
633
+ return !!input && typeof input === "object" && input.retried === true;
634
+ }
630
635
  function isErrorResponseBody(response) {
631
636
  var envErr = csrEnvelopeError(response);
632
637
  if (envErr !== void 0) response = envErr;
@@ -3298,6 +3303,7 @@ function mapHistoryListToMessages(list, platform, opts) {
3298
3303
  mapped.push(ph);
3299
3304
  } else if (isQueued) ; else if (isErrorResponse) {
3300
3305
  var em = { role: "assistant", content: getErrorMessage(response), isError: true };
3306
+ if (isRetriedFailure(response)) em.isRetried = true;
3301
3307
  if (item._fromBgChain) em._fromBgChain = true;
3302
3308
  if (item._isBgTask) em.isBackgroundTask = true;
3303
3309
  if (serverItemId !== void 0) em._serverItemId = serverItemId;
@@ -4326,7 +4332,9 @@ var ChatSession = class {
4326
4332
  timers = [];
4327
4333
  };
4328
4334
  var stopBase = base && typeof base.stop === "function" ? base.stop.bind(base) : null;
4335
+ var resolveRaced = null;
4329
4336
  var raced = new Promise(function(resolve, reject) {
4337
+ resolveRaced = resolve;
4330
4338
  base.then(function(res) {
4331
4339
  if (settled) return;
4332
4340
  settled = true;
@@ -4367,9 +4375,16 @@ var ChatSession = class {
4367
4375
  });
4368
4376
  });
4369
4377
  raced.stop = function() {
4378
+ if (settled) return;
4370
4379
  settled = true;
4371
4380
  clearProbes();
4372
- if (stopBase) stopBase();
4381
+ if (stopBase) {
4382
+ try {
4383
+ stopBase();
4384
+ } catch (e) {
4385
+ }
4386
+ }
4387
+ if (resolveRaced) resolveRaced({ id: itemId, status: "stopped" });
4373
4388
  };
4374
4389
  return raced;
4375
4390
  }
@@ -5382,6 +5397,7 @@ var ChatSession = class {
5382
5397
  }
5383
5398
  return response;
5384
5399
  }).then(function(response) {
5400
+ if (isPollStopped(response) || self._isCancelledPollResult(response)) return { content: "", isError: false, cancelled: true };
5385
5401
  if (isErrorResponseBody(response)) return { content: getErrorMessage(response), isError: true };
5386
5402
  var answer = params.aiPlatform === "openai" ? extractOpenAIText(response) : extractClaudeText(response);
5387
5403
  answer = (answer || "").trim();
@@ -5391,6 +5407,10 @@ var ChatSession = class {
5391
5407
  }).then(function(result) {
5392
5408
  delete self.pendingAgentRequests[params.key];
5393
5409
  if (dispatchItemId) self.historyItemPolls.delete(dispatchItemId);
5410
+ if (result.cancelled) {
5411
+ if (dispatchItemId) self._settleCancelledTurn(dispatchItemId, params.key);
5412
+ return result;
5413
+ }
5394
5414
  var reply = { role: "assistant", content: result.content, isError: result.isError };
5395
5415
  if (dispatchItemId) reply._serverItemId = dispatchItemId;
5396
5416
  self._applyReplyToCache(params.key, reply, dispatchItemId);
@@ -5869,8 +5889,9 @@ var ChatSession = class {
5869
5889
  }
5870
5890
  });
5871
5891
  Promise.resolve(run).catch(function() {
5872
- }).then(function() {
5892
+ }).then(function(result) {
5873
5893
  self.state.sending = false;
5894
+ if (result && result.cancelled) return;
5874
5895
  if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
5875
5896
  return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
5876
5897
  self.host.scrollToBottomIfSticky(true);
@@ -6049,6 +6070,10 @@ var ChatSession = class {
6049
6070
  }
6050
6071
  onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
6051
6072
  if (serverId) this.historyItemPolls.delete(serverId);
6073
+ if (this._isCancelledPollResult(response)) {
6074
+ if (serverId) this._settleCancelledTurn(serverId, ownerKey);
6075
+ return;
6076
+ }
6052
6077
  if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
6053
6078
  var offReply = isErrorResponseBody(response) ? { role: "assistant", content: getErrorMessage(response), isError: true } : { role: "assistant", content: ((platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "").trim() || "No text response received from AI provider." };
6054
6079
  this._applyReplyToCache(ownerKey, offReply, serverId);
@@ -6248,6 +6273,191 @@ var ChatSession = class {
6248
6273
  }
6249
6274
  });
6250
6275
  }
6276
+ /**
6277
+ * Stop waiting for the reply to the chat turn that is running now, and move on.
6278
+ *
6279
+ * `msg` is the turn's pending ASSISTANT bubble (the dot-trail "Thinking"
6280
+ * placeholder), which is where both views draw the control. A turn's two bubbles
6281
+ * carry the same _serverItemId, so it is also how the request is found.
6282
+ * cancelQueuedMessage is the sibling for a turn still waiting in the queue and
6283
+ * takes the USER bubble; the two differ in what they are handed and in what has
6284
+ * to be unwound afterwards, not in what the server is asked.
6285
+ *
6286
+ * What ends the wait:
6287
+ * 1. csr-cancel marks the running row cancelled. The worker stops relaying and
6288
+ * never stores an answer for it, so the reply cannot turn up later on this
6289
+ * device or another, and history replays the turn as cancelled;
6290
+ * 2. the poll on it is stopped, which settles whichever chain was awaiting it
6291
+ * with a stopped result: dispatchAgentRequest's for an immediate send, the
6292
+ * queued chain for a promoted turn, the history poll after a reload. Each
6293
+ * reads a stopped or cancelled result as "no reply" and releases what it
6294
+ * holds (state.sending, pendingAgentRequests, historyItemPolls), which is
6295
+ * what lets the next send go out immediately instead of queueing;
6296
+ * 3. the bubbles settle at once, without waiting for that chain: the user
6297
+ * bubble takes its cancelled form, the placeholder goes, and the next queued
6298
+ * turn, if there is one, is promoted so its own "Thinking" appears.
6299
+ *
6300
+ * What it does NOT do: the provider call already in flight on the worker runs
6301
+ * to its end (a streamed one stops within about a second). A message sent
6302
+ * behind it on the same queue starts once the worker acks the cancelled row.
6303
+ *
6304
+ * Refused without a server id (the ack has not come back yet; the views disable
6305
+ * the control until it has) and while a cancel is already out. Indexing passes
6306
+ * are not this method's business: their Stop is cancelIndexingGroup, which ends
6307
+ * the file's whole chain.
6308
+ */
6309
+ cancelPendingReply(msg, idx) {
6310
+ var self = this;
6311
+ var id = this.host.getIdentity();
6312
+ var serverId = msg._serverItemId;
6313
+ if (!serverId || msg._cancelling) return;
6314
+ if (msg.role !== "assistant" || !msg.isPending || msg.isBackgroundTask) return;
6315
+ var platform = id.platform;
6316
+ if (platform !== "claude" && platform !== "openai") return;
6317
+ var url = platform === "claude" ? ANTHROPIC_MESSAGES_API_URL : OPENAI_RESPONSES_API_URL;
6318
+ var queueBase = id.userId || id.projectId;
6319
+ var queue = msg._useBgQueue ? bgIndexingQueueName(queueBase) : queueBase;
6320
+ var findPlaceholder = function() {
6321
+ return self.state.messages.findIndex(function(m) {
6322
+ return m._serverItemId === serverId && m.role === "assistant" && !!m.isPending;
6323
+ });
6324
+ };
6325
+ var at = this.state.messages[idx] && this.state.messages[idx]._serverItemId === serverId && this.state.messages[idx].role === "assistant" ? idx : findPlaceholder();
6326
+ if (at !== -1) {
6327
+ this.state.messages[at] = Object.assign({}, this.state.messages[at], { _cancelling: true, _cancelError: void 0 });
6328
+ }
6329
+ this.host.notify();
6330
+ var unmark = function(errMsg) {
6331
+ var ci = findPlaceholder();
6332
+ if (ci === -1) return;
6333
+ self.state.messages[ci] = Object.assign({}, self.state.messages[ci], { _cancelling: false, _cancelError: errMsg });
6334
+ self.host.notify();
6335
+ };
6336
+ Promise.resolve(this.host.cancelRequest({
6337
+ url,
6338
+ method: "POST",
6339
+ id: serverId,
6340
+ queue,
6341
+ service: id.projectId,
6342
+ owner: id.owner
6343
+ })).then(function(result) {
6344
+ if (result && result.removed) {
6345
+ self._stopPoll(serverId);
6346
+ self._settleCancelledTurn(serverId);
6347
+ return;
6348
+ }
6349
+ unmark(void 0);
6350
+ }).catch(function(err) {
6351
+ unmark(err && typeof err.message === "string" && err.message ? err.message : "Could not stop this request.");
6352
+ });
6353
+ }
6354
+ /** The cancelled form of a request bubble, with everything that keeps it in
6355
+ * place carried over: the file markers that hold an indexing pass in its
6356
+ * collapsed row, the chat it belongs to, its send time and its render key. */
6357
+ _cancelledCopyOf(m, itemId) {
6358
+ var cancelled = { role: "user", content: m.content, isCancelled: true };
6359
+ if (itemId !== void 0) cancelled._serverItemId = itemId;
6360
+ else if (m._serverItemId !== void 0) cancelled._serverItemId = m._serverItemId;
6361
+ if (m.isBackgroundTask) cancelled.isBackgroundTask = true;
6362
+ if (m._indexFile) cancelled._indexFile = m._indexFile;
6363
+ if (m._useBgQueue) cancelled._useBgQueue = true;
6364
+ if (m._ownerKey !== void 0) cancelled._ownerKey = m._ownerKey;
6365
+ if (m._ts !== void 0) cancelled._ts = m._ts;
6366
+ if (m._localId !== void 0) cancelled._localId = m._localId;
6367
+ return cancelled;
6368
+ }
6369
+ /**
6370
+ * Settle a turn as cancelled wherever it lives, and let the queue move on.
6371
+ *
6372
+ * On the chat on screen: every pending placeholder carrying the id goes (a
6373
+ * history refetch can have re-mapped the running turn into a second one), the
6374
+ * request bubble takes its cancelled form, and the next queued turn is promoted
6375
+ * so the conversation does not sit on an "(In queue)" bubble with nothing
6376
+ * running. For a chat that is NOT on screen (the user moved on while the turn
6377
+ * ran, and it was cancelled from elsewhere) the same edit is made in that chat's
6378
+ * history cache, which is what the next visit renders.
6379
+ *
6380
+ * Idempotent on purpose: a cancel this client made settles the bubbles at once,
6381
+ * and the chain that was awaiting the poll settles again a moment later when the
6382
+ * stop resolves it. The second pass finds nothing to do. Deliberately NOT
6383
+ * _removeStrayPendingAssistants: by then the placeholder on screen is the freshly
6384
+ * promoted turn's, whose user bubble is pending, and the sweep would take it.
6385
+ */
6386
+ _settleCancelledTurn(itemId, ownerKey) {
6387
+ if (!itemId) return;
6388
+ if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
6389
+ this._applyCancelToCache(ownerKey, itemId);
6390
+ return;
6391
+ }
6392
+ var changed = false, wasBg = false;
6393
+ for (var i = this.state.messages.length - 1; i >= 0; i--) {
6394
+ var m = this.state.messages[i];
6395
+ if (!m || m._serverItemId !== itemId) continue;
6396
+ if (m.role === "assistant" && m.isPending) {
6397
+ this.state.messages.splice(i, 1);
6398
+ changed = true;
6399
+ continue;
6400
+ }
6401
+ if (m.role === "user" && !m.isCancelled) {
6402
+ if (m.isBackgroundTask) wasBg = true;
6403
+ this.state.messages[i] = this._cancelledCopyOf(m, itemId);
6404
+ changed = true;
6405
+ }
6406
+ }
6407
+ this.cancelledServerIds.delete(itemId);
6408
+ if (!changed) return;
6409
+ if (wasBg) this.promoteNextBgQueuedToRunning();
6410
+ else this.promoteNextQueuedToRunning();
6411
+ this.updateHistoryCache();
6412
+ this.host.notify();
6413
+ }
6414
+ /**
6415
+ * _settleCancelledTurn for a chat that is not on screen: the same edit, made in
6416
+ * its history cache. Matched by id where the cached bubbles carry one; an
6417
+ * off-chat send caches its pair before the ack (see dispatchComposedMessage), so
6418
+ * the fallback is positional, exactly as _applyReplyToCache's is: the trailing
6419
+ * pending placeholder, and the last request bubble still marked in flight.
6420
+ */
6421
+ _applyCancelToCache(key, itemId) {
6422
+ var existing = key ? this.aiChatHistoryCache[key] : void 0;
6423
+ if (!existing) return;
6424
+ var msgs = existing.messages.slice();
6425
+ var changed = false;
6426
+ var pIdx = -1, uIdx = -1;
6427
+ for (var i = msgs.length - 1; i >= 0; i--) {
6428
+ var m = msgs[i];
6429
+ if (!m || m._serverItemId !== itemId) continue;
6430
+ if (m.role === "assistant" && m.isPending) {
6431
+ msgs.splice(i, 1);
6432
+ changed = true;
6433
+ pIdx = i;
6434
+ } else if (m.role === "user" && !m.isCancelled && uIdx === -1) uIdx = i;
6435
+ }
6436
+ if (pIdx === -1) {
6437
+ for (var p = msgs.length - 1; p >= 0; p--) {
6438
+ var pm = msgs[p];
6439
+ if (!pm || !pm.isPending || pm.role !== "assistant" || pm.isBackgroundTask || pm._serverItemId !== void 0) continue;
6440
+ msgs.splice(p, 1);
6441
+ changed = true;
6442
+ break;
6443
+ }
6444
+ }
6445
+ if (uIdx === -1) {
6446
+ for (var u = msgs.length - 1; u >= 0; u--) {
6447
+ var um = msgs[u];
6448
+ if (!um || um.role !== "user" || um.isBackgroundTask || um._serverItemId !== void 0 || um._stageId) continue;
6449
+ if (!(um.isPendingQueued || um.isPendingInProcess || um.isSendingToServer)) continue;
6450
+ uIdx = u;
6451
+ break;
6452
+ }
6453
+ }
6454
+ if (uIdx !== -1) {
6455
+ msgs[uIdx] = this._cancelledCopyOf(msgs[uIdx], itemId);
6456
+ changed = true;
6457
+ }
6458
+ if (!changed) return;
6459
+ this.aiChatHistoryCache[key] = { messages: msgs, endOfList: existing.endOfList, startKeyHistory: existing.startKeyHistory };
6460
+ }
6251
6461
  /**
6252
6462
  * Stop indexing a file, from its collapsed row — every pass at once, not just
6253
6463
  * the bubble the user happens to see.
@@ -6314,6 +6524,7 @@ var ChatSession = class {
6314
6524
  }
6315
6525
  }
6316
6526
  this._adoptWorkerIndexingPasses(0);
6527
+ this._sweepCancelledIndexing();
6317
6528
  var ids = group.cancellableIds || [];
6318
6529
  if (!ids.length) {
6319
6530
  this.host.notify();
@@ -6596,9 +6807,10 @@ var ChatSession = class {
6596
6807
  this.state.sending = true;
6597
6808
  this.host.scrollToBottomIfSticky(true);
6598
6809
  return Promise.resolve(pending).catch(function() {
6599
- }).then(function() {
6810
+ }).then(function(result) {
6600
6811
  if (token !== self.state.gateRefreshToken) return;
6601
6812
  self.state.sending = false;
6813
+ if (result && result.cancelled) return;
6602
6814
  return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
6603
6815
  self.host.scrollToBottomIfSticky(true);
6604
6816
  });
@@ -8227,8 +8439,19 @@ function buildChatDisplayList(messages, opts) {
8227
8439
  } else if (stopped) {
8228
8440
  grp.status = "cancelled";
8229
8441
  } else {
8230
- var last = grp.members[grp.members.length - 1].msg;
8231
- grp.status = last.isError ? "error" : "done";
8442
+ var retriedIds = {};
8443
+ for (var ri = 0; ri < grp.members.length; ri++) {
8444
+ var rm = grp.members[ri].msg;
8445
+ if (rm.isRetried && rm._serverItemId) retriedIds[rm._serverItemId] = true;
8446
+ }
8447
+ var li = grp.members.length - 1;
8448
+ while (li >= 0 && (grp.members[li].msg.isRetried || grp.members[li].msg._serverItemId && retriedIds[grp.members[li].msg._serverItemId])) li--;
8449
+ if (li < 0) {
8450
+ grp.status = "active";
8451
+ } else {
8452
+ var last = grp.members[li].msg;
8453
+ grp.status = last.isError ? "error" : "done";
8454
+ }
8232
8455
  }
8233
8456
  var sawFirstPass = false;
8234
8457
  for (var pi = 0; pi < grp.members.length; pi++) {
@@ -8693,6 +8916,7 @@ exports.isOfficeFile = isOfficeFile;
8693
8916
  exports.isPagedReadFile = isPagedReadFile;
8694
8917
  exports.isPreviewableImagePath = isPreviewableImagePath;
8695
8918
  exports.isProviderApiKeyError = isProviderApiKeyError;
8919
+ exports.isRetriedFailure = isRetriedFailure;
8696
8920
  exports.isServerExtractable = isServerExtractable;
8697
8921
  exports.isServiceDbAttachmentHref = isServiceDbAttachmentHref;
8698
8922
  exports.isWindowedReadFile = isWindowedReadFile;