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/README.md +5 -0
- package/bunnyquery.css +59 -0
- package/bunnyquery.js +246 -6
- package/dist/engine.cjs +229 -5
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +79 -1
- package/dist/engine.d.ts +79 -1
- package/dist/engine.mjs +229 -6
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/errors.ts +15 -0
- package/src/engine/history.ts +5 -1
- package/src/engine/host.ts +4 -0
- package/src/engine/index.ts +1 -0
- package/src/engine/indexing_groups.ts +24 -3
- package/src/engine/session.ts +239 -3
- package/styles/chat.css +59 -0
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)
|
|
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
|
|
8231
|
-
|
|
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;
|