bunnyquery 1.7.0 → 1.8.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/bunnyquery.css +12 -0
- package/bunnyquery.js +337 -41
- package/dist/engine.cjs +351 -35
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +214 -12
- package/dist/engine.d.ts +214 -12
- package/dist/engine.mjs +340 -36
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/ai_agent.ts +80 -0
- package/src/engine/budget.ts +113 -7
- package/src/engine/history.ts +58 -18
- package/src/engine/host.ts +6 -0
- package/src/engine/index.ts +9 -0
- package/src/engine/indexing_groups.ts +50 -11
- package/src/engine/links.ts +35 -1
- package/src/engine/prompts/chat_system_prompt.ts +3 -0
- package/src/engine/requests.ts +11 -2
- package/src/engine/session.ts +220 -6
- package/src/engine/time.ts +34 -0
- package/styles/chat.css +12 -0
package/dist/engine.d.mts
CHANGED
|
@@ -312,18 +312,44 @@ declare function isAuthExpiredError(input: any): boolean;
|
|
|
312
312
|
|
|
313
313
|
declare var CONTEXT_WINDOW_DEFAULT: Record<string, number>;
|
|
314
314
|
declare var CONTEXT_WINDOW_BY_MODEL: Record<string, number>;
|
|
315
|
+
/**
|
|
316
|
+
* Record context windows from a provider models listing. Accepts the raw list
|
|
317
|
+
* items and reads `max_input_tokens` (Anthropic); items without it are skipped,
|
|
318
|
+
* so passing an OpenAI listing is a no-op rather than an error.
|
|
319
|
+
*/
|
|
320
|
+
declare function registerModelContextWindows(models: Array<{
|
|
321
|
+
id?: string;
|
|
322
|
+
max_input_tokens?: number;
|
|
323
|
+
}> | null | undefined): void;
|
|
324
|
+
declare function setProjectContextWindow(serviceId: string, tokens: number | null | undefined): void;
|
|
325
|
+
declare function getProjectContextWindow(serviceId: string): number | null;
|
|
315
326
|
declare var OUTPUT_TOKEN_RESERVE: number;
|
|
316
327
|
declare var TOOL_AND_RESPONSE_BUFFER: number;
|
|
317
328
|
declare var MIN_INPUT_TOKEN_BUDGET: number;
|
|
318
329
|
declare var CLAUDE_PER_REQUEST_INPUT_CAP: number;
|
|
319
330
|
declare var MAX_HISTORY_MESSAGES: number;
|
|
320
331
|
declare var HISTORY_TOKEN_BUDGET: number;
|
|
332
|
+
declare var CLAUDE_INPUT_CAP_RATIO: number;
|
|
333
|
+
declare var HISTORY_BUDGET_RATIO: number;
|
|
321
334
|
declare function estimateTextTokens(text: string): number;
|
|
322
335
|
declare function estimateMessageTokens(msg: {
|
|
323
336
|
role: string;
|
|
324
337
|
content: string;
|
|
325
338
|
}): number;
|
|
326
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Resolve a model's context window, most specific source first:
|
|
341
|
+
* 1. per-project override (project settings)
|
|
342
|
+
* 2. the provider's own models listing (Anthropic `max_input_tokens`)
|
|
343
|
+
* 3. an exact entry in CONTEXT_WINDOW_BY_MODEL
|
|
344
|
+
* 4. a family entry, by dropping trailing '-' segments off the id
|
|
345
|
+
* 5. the platform default
|
|
346
|
+
*
|
|
347
|
+
* Step 4 is why a new or suffixed id no longer drops straight to the platform
|
|
348
|
+
* default: 'gpt-5.6-luna' resolves via 'gpt-5.6', and a dated Claude snapshot
|
|
349
|
+
* such as 'claude-opus-4-7-20260101' resolves via 'claude-opus-4-7'. The walk
|
|
350
|
+
* stops at the first hit, so a more specific entry always wins over its family.
|
|
351
|
+
*/
|
|
352
|
+
declare function getContextWindow(platform: string, model?: string, serviceId?: string): number;
|
|
327
353
|
declare function stripFileBlocksFromHistory(content: string): string;
|
|
328
354
|
type BoundedChatOptions = {
|
|
329
355
|
platform: string;
|
|
@@ -423,6 +449,21 @@ declare function isHttpUrlLike(target: string): boolean;
|
|
|
423
449
|
* space in it, and deleting it points at a file that does not exist.
|
|
424
450
|
*/
|
|
425
451
|
declare function repairUrlWhitespace(href: string): string;
|
|
452
|
+
/**
|
|
453
|
+
* A model reproducing a URL sometimes HTML-escapes its `&` query separators as
|
|
454
|
+
* `&` (or the numeric `&` / `&`). Left in the href that escaping
|
|
455
|
+
* survives the client's own escapeAttr -> v-html/innerHTML decode round-trip and
|
|
456
|
+
* reaches the browser LITERALLY, so a presigned S3 URL is navigated with its
|
|
457
|
+
* parameters named `amp;Signature`, `amp;Expires`, `amp;response-content-type`,
|
|
458
|
+
* ... — the real params vanish, the signature can't be located, and S3 rejects
|
|
459
|
+
* it (the "링크가 안되" dead export link). Undo just that entity escaping.
|
|
460
|
+
*
|
|
461
|
+
* This is a no-op on a clean URL: a valid link carries a raw `&` between params
|
|
462
|
+
* and percent-encodes (`%26`) any literal ampersand that is data, so a real URL
|
|
463
|
+
* never contains `&` to begin with. Mirrors repairUrlWhitespace: it repairs
|
|
464
|
+
* model damage, not the URL. The loop collapses a doubly-escaped `&amp;` too.
|
|
465
|
+
*/
|
|
466
|
+
declare function repairUrlEntities(href: string): string;
|
|
426
467
|
/**
|
|
427
468
|
* Trim punctuation and unmatched wrappers that cling to a token in prose.
|
|
428
469
|
* `src::a/b.pdf).` -> `src::a/b.pdf`, while a balanced `file (v2).pdf` is kept.
|
|
@@ -466,9 +507,78 @@ declare function classifyInlineLink(full: string, groups: Array<string | undefin
|
|
|
466
507
|
} | null;
|
|
467
508
|
declare function truncateLabelForDisplay(label: string): string;
|
|
468
509
|
|
|
510
|
+
/**
|
|
511
|
+
* Chat timestamp formatting, shared so agent.vue and the widget render an
|
|
512
|
+
* identical "small text under the bubble". Pure and locale-aware: it formats a
|
|
513
|
+
* given epoch-ms value, it never reads the current time, so it stays testable and
|
|
514
|
+
* DOM-free.
|
|
515
|
+
*/
|
|
516
|
+
/** Wall-clock epoch ms. Separate from the engine's monotonic nowMs() (which is
|
|
517
|
+
* performance.now() when available and therefore NOT epoch): a displayed
|
|
518
|
+
* timestamp must be wall time. */
|
|
519
|
+
declare function wallClockNow(): number;
|
|
520
|
+
/**
|
|
521
|
+
* "Jul 24, 2026, 3:42:07 PM" (locale-formatted). Empty string for a missing or
|
|
522
|
+
* non-finite value, so a caller can gate rendering on the result being truthy and
|
|
523
|
+
* a pending bubble (no timestamp yet) simply shows nothing.
|
|
524
|
+
*/
|
|
525
|
+
declare function formatChatTimestamp(ms?: number): string;
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* The `ai_agent` service option, parsed and serialized in ONE place.
|
|
529
|
+
*
|
|
530
|
+
* Stored on the skapi service record as up to three '#'-delimited segments:
|
|
531
|
+
*
|
|
532
|
+
* none AI chat disabled
|
|
533
|
+
* claude platform chosen, no model saved yet
|
|
534
|
+
* claude#claude-sonnet-4-6 platform + model
|
|
535
|
+
* claude#claude-sonnet-4-6#400000 platform + model + context-window override
|
|
536
|
+
*
|
|
537
|
+
* The third segment is new and optional, so every value written before it
|
|
538
|
+
* existed parses unchanged with `contextWindow: null`. Nothing writes a third
|
|
539
|
+
* segment without a model, because the window is meaningless without one.
|
|
540
|
+
*
|
|
541
|
+
* This lived as four separate copies (agent.vue, dbfile.vue, service.vue, and
|
|
542
|
+
* the widget's index.js), which is how the format drifts. New callers should
|
|
543
|
+
* import from here rather than re-deriving the split.
|
|
544
|
+
*/
|
|
545
|
+
type AiAgentPlatform = 'claude' | 'openai' | null;
|
|
546
|
+
type ParsedAiAgent = {
|
|
547
|
+
/** null when unset or explicitly 'none'. */
|
|
548
|
+
platform: AiAgentPlatform;
|
|
549
|
+
/** '' when no model has been saved. */
|
|
550
|
+
model: string;
|
|
551
|
+
/** Per-project context-window override in tokens, or null to use the model's. */
|
|
552
|
+
contextWindow: number | null;
|
|
553
|
+
/** True when a real platform is configured (i.e. not unset and not 'none'). */
|
|
554
|
+
hasPlatform: boolean;
|
|
555
|
+
};
|
|
556
|
+
declare function parseAiAgentValue(value: string | null | undefined): ParsedAiAgent;
|
|
557
|
+
declare function buildAiAgentValue(platform: string | null | undefined, model?: string | null, contextWindow?: number | null): string;
|
|
558
|
+
|
|
469
559
|
declare function filterListByClearHorizon(list: any[], clearedAt: number): any[];
|
|
470
560
|
declare function normalizeTextContent(content: any): string;
|
|
471
561
|
declare function extractLastUserTextFromRequest(requestBody: any): string;
|
|
562
|
+
/** The two openings an indexing prompt can have. A bg-queue item that starts with
|
|
563
|
+
* neither is an ordinary chat that happened to be routed onto that queue. */
|
|
564
|
+
declare function isIndexingRequestText(userText: any): boolean;
|
|
565
|
+
type IndexingRequestRef = {
|
|
566
|
+
name: string;
|
|
567
|
+
path?: string;
|
|
568
|
+
mime?: string;
|
|
569
|
+
size?: number;
|
|
570
|
+
/** A CONTINUE pass rather than the run's first. */
|
|
571
|
+
continued: boolean;
|
|
572
|
+
};
|
|
573
|
+
/**
|
|
574
|
+
* The file an indexing prompt is about, read back out of the prompt itself.
|
|
575
|
+
*
|
|
576
|
+
* The prompt is the only description of the pass that survives on the server, so
|
|
577
|
+
* this is how BOTH a history rebuild and a worker-minted pass the client never
|
|
578
|
+
* dispatched (ChatSession._adoptWorkerIndexingPasses) recover the file. Shared so
|
|
579
|
+
* the two produce the same `_indexFile`, which is what makes them group together.
|
|
580
|
+
*/
|
|
581
|
+
declare function parseIndexingRequestText(userText: any): IndexingRequestRef | null;
|
|
472
582
|
type MapHistoryOptions = {
|
|
473
583
|
clearedAt: number;
|
|
474
584
|
serviceId: string;
|
|
@@ -681,11 +791,20 @@ type BgTaskEntry = {
|
|
|
681
791
|
/** How many CONTINUE passes have already run for this file (resume-across-passes). */
|
|
682
792
|
resumePass?: number;
|
|
683
793
|
};
|
|
794
|
+
/**
|
|
795
|
+
* `queue` narrows the fetch to one processing chain; `status` narrows it to items
|
|
796
|
+
* in one state. Passing both is how the client asks "is there still unresolved
|
|
797
|
+
* work on the background-indexing queue?" without pulling a page of chat history
|
|
798
|
+
* (see ChatSession._adoptWorkerIndexingPasses) — the server answers that from a
|
|
799
|
+
* status-keyed index, so the reply carries only the live items, not the bodies of
|
|
800
|
+
* everything already finished.
|
|
801
|
+
*/
|
|
684
802
|
declare function getChatHistory(params: {
|
|
685
803
|
service?: string;
|
|
686
804
|
owner?: string;
|
|
687
805
|
platform: 'claude' | 'openai';
|
|
688
806
|
queue?: string;
|
|
807
|
+
status?: 'pending' | 'running' | 'resolved' | 'failed';
|
|
689
808
|
}, fetchOptions: Record<string, any>): Promise<any>;
|
|
690
809
|
|
|
691
810
|
/**
|
|
@@ -753,6 +872,12 @@ interface ChatMessage {
|
|
|
753
872
|
_localId?: string;
|
|
754
873
|
_cancelling?: boolean;
|
|
755
874
|
_cancelError?: string;
|
|
875
|
+
/** Epoch ms shown as small text under the bubble. From the request history a
|
|
876
|
+
* USER bubble carries the request's `created` time and an ASSISTANT bubble the
|
|
877
|
+
* `updated` (response) time; a live bubble is stamped with the wall clock when
|
|
878
|
+
* it is created, then reconciled to the server value on the next history load.
|
|
879
|
+
* Absent while a turn is still pending, so no time shows on a "Thinking" bubble. */
|
|
880
|
+
_ts?: number;
|
|
756
881
|
_ownerKey?: string;
|
|
757
882
|
}
|
|
758
883
|
interface ChatState {
|
|
@@ -845,12 +970,34 @@ interface ChatHost {
|
|
|
845
970
|
* repeating forever, and any real question the user asks in between gets buried.
|
|
846
971
|
*
|
|
847
972
|
* buildChatDisplayList turns the flat message array into a DISPLAY list in which
|
|
848
|
-
* every message belonging to one file (however far apart
|
|
849
|
-
* else is interleaved between them) is represented by a single
|
|
850
|
-
* rendered at the position of that
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
973
|
+
* every message belonging to one run of one file (however far apart the passes
|
|
974
|
+
* sit, and whatever else is interleaved between them) is represented by a single
|
|
975
|
+
* group entry, rendered at the position of that run's FIRST loaded pass.
|
|
976
|
+
*
|
|
977
|
+
* First, not newest. The message array is ordered by request CREATION time, and
|
|
978
|
+
* a run's later passes are created one at a time as the previous one resolves —
|
|
979
|
+
* by the client for the paged text path, and by the WORKER itself for the
|
|
980
|
+
* rendered-page (PDF) path, which the client only learns about on its next
|
|
981
|
+
* first-page history fetch. So a pass is routinely created minutes after the
|
|
982
|
+
* upload, on a queue that runs in parallel with the foreground chat. Anchoring
|
|
983
|
+
* at the newest pass meant one such late pass dragged the whole run — every
|
|
984
|
+
* earlier pass with it — below any question the user had asked in the meantime,
|
|
985
|
+
* and made a run that had visibly finished before the question render after it.
|
|
986
|
+
* Anchoring at the first pass puts the row where the run actually began, which
|
|
987
|
+
* is a position no later pass can change:
|
|
988
|
+
* - a new pass never relocates the row, so nothing under the reader shifts;
|
|
989
|
+
* - a question asked after the upload always renders below the row, which is
|
|
990
|
+
* the order it happened in.
|
|
991
|
+
* The cost is that a long-running index does not follow the conversation to the
|
|
992
|
+
* bottom: once the user has chatted past the upload, the spinner and the Stop
|
|
993
|
+
* button sit above their newest turns. That is a scroll away, and the row no
|
|
994
|
+
* longer lies about when the work started.
|
|
995
|
+
*
|
|
996
|
+
* Paging older history is the one thing that CAN move the row: an older page
|
|
997
|
+
* carrying earlier passes of a run already on screen moves it up into that page.
|
|
998
|
+
* That happens at most once per run, only for a run whose start was never
|
|
999
|
+
* loaded (`mayHaveOlder`), and it is the same event that already re-derives
|
|
1000
|
+
* `runKey` — so the view treats it as a new row either way.
|
|
854
1001
|
*
|
|
855
1002
|
* The group deliberately reports no authoritative pass TOTAL. History is paged
|
|
856
1003
|
* newest-first, so any total computed from loaded messages is a lower bound that
|
|
@@ -873,9 +1020,10 @@ type IndexingGroup = {
|
|
|
873
1020
|
* Monday and re-indexed on Wednesday is two runs, and collapsing them into
|
|
874
1021
|
* one row erased Monday's from Monday's place in the conversation, claimed
|
|
875
1022
|
* its passes for Wednesday, and let Monday's failure be overwritten by
|
|
876
|
-
* Wednesday's success.
|
|
877
|
-
*
|
|
878
|
-
*
|
|
1023
|
+
* Wednesday's success. Named after the run's FIRST loaded pass (see where it
|
|
1024
|
+
* is assigned below), so passes appended to the run and other runs appearing
|
|
1025
|
+
* on either side of it never rename a row already on screen. This is the
|
|
1026
|
+
* render key and the expansion key. */
|
|
879
1027
|
runKey: string;
|
|
880
1028
|
name: string;
|
|
881
1029
|
path?: string;
|
|
@@ -904,8 +1052,16 @@ type IndexingGroup = {
|
|
|
904
1052
|
/** The file's first pass is not among the loaded messages, so earlier passes
|
|
905
1053
|
* exist in history that has not been paged in yet. */
|
|
906
1054
|
mayHaveOlder: boolean;
|
|
907
|
-
/** Position in the source array this collapsed row renders at
|
|
1055
|
+
/** Position in the source array this collapsed row renders at: the index of
|
|
1056
|
+
* the run's FIRST loaded pass (see the file docstring for why not the last). */
|
|
908
1057
|
anchorIndex: number;
|
|
1058
|
+
/** Identity of the turn at `anchorIndex` — its server item id, or its local id
|
|
1059
|
+
* while it has none, or `''` when it has neither. The views stamp this on the
|
|
1060
|
+
* row (`data-row-pos`) so the scroll anchor can tell a row that RELOCATED (an
|
|
1061
|
+
* older page moved the run's start) from one that merely gained a pass. They
|
|
1062
|
+
* must not re-derive it from `members`: which member the row renders at is
|
|
1063
|
+
* this module's decision, and the two silently disagreed once already. */
|
|
1064
|
+
anchorId: string;
|
|
909
1065
|
};
|
|
910
1066
|
type DisplayEntry = {
|
|
911
1067
|
kind: 'message';
|
|
@@ -1083,6 +1239,9 @@ declare class ChatSession {
|
|
|
1083
1239
|
_clearPendingUserBubble(itemId: string): void;
|
|
1084
1240
|
resumePendingRequest(token: number): Promise<void>;
|
|
1085
1241
|
handleHistoryItemResolution(itemId: string, response: any, platform: string): void;
|
|
1242
|
+
/** The file an already-rendered background pass is about, off its request
|
|
1243
|
+
* bubble. Null for an ordinary turn, which is most of them. */
|
|
1244
|
+
private _indexRefOfItem;
|
|
1086
1245
|
applyHistoryItemResolution(itemId: string, response: any, platform: string): void;
|
|
1087
1246
|
/** How a bg task maps onto a collapsed row: the row's own key (storage path
|
|
1088
1247
|
* when known, else the filename), scoped to the chat it belongs to. A storage
|
|
@@ -1114,6 +1273,49 @@ declare class ChatSession {
|
|
|
1114
1273
|
* Runs from drainBgTaskQueue, which both clients call after a history load.
|
|
1115
1274
|
*/
|
|
1116
1275
|
private _sweepCancelledIndexing;
|
|
1276
|
+
/**
|
|
1277
|
+
* True when the WORKER, not this client, drives the rest of this file's chain.
|
|
1278
|
+
* The mirror image of the early returns in maybeResumeIndexing: whatever that
|
|
1279
|
+
* refuses to continue is exactly what nothing client-side is tracking.
|
|
1280
|
+
*/
|
|
1281
|
+
private _isWorkerDrivenIndexing;
|
|
1282
|
+
/**
|
|
1283
|
+
* Pick up indexing passes the WORKER minted, which no client ever dispatched.
|
|
1284
|
+
*
|
|
1285
|
+
* For a PDF (and for text/grid when windowed indexing is on) the worker writes
|
|
1286
|
+
* pass N+1's row itself, inside pass N's invocation, right after saving pass
|
|
1287
|
+
* N's result. That row reaches this client through nothing at all: it is not in
|
|
1288
|
+
* bgTaskQueue (the client never asked for it) and it is not in state.messages
|
|
1289
|
+
* (only a first-page history load maps it in, which happens on mount, project
|
|
1290
|
+
* switch or tab return). So between two worker passes every pass the client
|
|
1291
|
+
* knows about is settled, and the collapsed row renders "Indexed N passes"
|
|
1292
|
+
* with no spinner and no Stop, for a file that is still being read. A user who
|
|
1293
|
+
* believes that then asks questions against a half-indexed file — which is what
|
|
1294
|
+
* this exists to prevent.
|
|
1295
|
+
*
|
|
1296
|
+
* So when a background indexing pass settles, ask the bg queue what is still
|
|
1297
|
+
* unresolved on it and adopt anything unknown as an ordinary BgTaskEntry.
|
|
1298
|
+
* drainBgTaskQueue then treats it exactly like a pass this client dispatched:
|
|
1299
|
+
* same bubble, same `_indexFile` (so it joins the file's collapsed row), same
|
|
1300
|
+
* poll — and that poll settling runs this again, so the chain is followed to
|
|
1301
|
+
* its end. `status`-scoped so the reply carries the live items only, never a
|
|
1302
|
+
* page of finished ones with their bodies.
|
|
1303
|
+
*
|
|
1304
|
+
* Termination: the only trigger is a pass SETTLING, which happens once per
|
|
1305
|
+
* pass. An adopted item that is still running is not re-adopted (its id is
|
|
1306
|
+
* already polled), and when the queue holds nothing unknown the chain stops on
|
|
1307
|
+
* its own. Nothing here is periodic — a timer that re-reads history is the
|
|
1308
|
+
* shape that previously looped fetchHistoryPage after an already-DONE index.
|
|
1309
|
+
*/
|
|
1310
|
+
private _adoptingWorkerPasses;
|
|
1311
|
+
private _adoptWorkerIndexingPasses;
|
|
1312
|
+
/** Any of these ids still queued or still polled, i.e. surviving work. */
|
|
1313
|
+
private _isTrackingAny;
|
|
1314
|
+
/** One live bg-queue item -> a BgTaskEntry, if it is an indexing pass this
|
|
1315
|
+
* client is not already tracking. Returns whether it was adopted. */
|
|
1316
|
+
private _adoptWorkerIndexingItem;
|
|
1317
|
+
/** Follow the chain on from a background indexing pass that just settled. */
|
|
1318
|
+
private _followWorkerIndexingChain;
|
|
1117
1319
|
/** Best-effort server-side cancel of a bg-queue item that has no bubble (so
|
|
1118
1320
|
* cancelQueuedMessage, which drives one, has nothing to act on). */
|
|
1119
1321
|
private _cancelServerItem;
|
|
@@ -1134,4 +1336,4 @@ declare class ChatSession {
|
|
|
1134
1336
|
bumpGate(): void;
|
|
1135
1337
|
}
|
|
1136
1338
|
|
|
1137
|
-
export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, type DisplayEntry, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, type PinnedDispatchContext, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAttachmentContent, parseIndexingLabel, readExpiredAttachmentHref, registerAttachmentParser, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
|
|
1339
|
+
export { type AiAgentPlatform, type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_INPUT_CAP_RATIO, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, type DisplayEntry, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_BUDGET_RATIO, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingRequestRef, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, type ParsedAiAgent, type PinnedDispatchContext, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildAiAgentValue, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, getProjectContextWindow, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isIndexingRequestText, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAiAgentValue, parseAttachmentContent, parseIndexingLabel, parseIndexingRequestText, readExpiredAttachmentHref, registerAttachmentParser, registerModelContextWindows, repairUrlEntities, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, setProjectContextWindow, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, wallClockNow };
|
package/dist/engine.d.ts
CHANGED
|
@@ -312,18 +312,44 @@ declare function isAuthExpiredError(input: any): boolean;
|
|
|
312
312
|
|
|
313
313
|
declare var CONTEXT_WINDOW_DEFAULT: Record<string, number>;
|
|
314
314
|
declare var CONTEXT_WINDOW_BY_MODEL: Record<string, number>;
|
|
315
|
+
/**
|
|
316
|
+
* Record context windows from a provider models listing. Accepts the raw list
|
|
317
|
+
* items and reads `max_input_tokens` (Anthropic); items without it are skipped,
|
|
318
|
+
* so passing an OpenAI listing is a no-op rather than an error.
|
|
319
|
+
*/
|
|
320
|
+
declare function registerModelContextWindows(models: Array<{
|
|
321
|
+
id?: string;
|
|
322
|
+
max_input_tokens?: number;
|
|
323
|
+
}> | null | undefined): void;
|
|
324
|
+
declare function setProjectContextWindow(serviceId: string, tokens: number | null | undefined): void;
|
|
325
|
+
declare function getProjectContextWindow(serviceId: string): number | null;
|
|
315
326
|
declare var OUTPUT_TOKEN_RESERVE: number;
|
|
316
327
|
declare var TOOL_AND_RESPONSE_BUFFER: number;
|
|
317
328
|
declare var MIN_INPUT_TOKEN_BUDGET: number;
|
|
318
329
|
declare var CLAUDE_PER_REQUEST_INPUT_CAP: number;
|
|
319
330
|
declare var MAX_HISTORY_MESSAGES: number;
|
|
320
331
|
declare var HISTORY_TOKEN_BUDGET: number;
|
|
332
|
+
declare var CLAUDE_INPUT_CAP_RATIO: number;
|
|
333
|
+
declare var HISTORY_BUDGET_RATIO: number;
|
|
321
334
|
declare function estimateTextTokens(text: string): number;
|
|
322
335
|
declare function estimateMessageTokens(msg: {
|
|
323
336
|
role: string;
|
|
324
337
|
content: string;
|
|
325
338
|
}): number;
|
|
326
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Resolve a model's context window, most specific source first:
|
|
341
|
+
* 1. per-project override (project settings)
|
|
342
|
+
* 2. the provider's own models listing (Anthropic `max_input_tokens`)
|
|
343
|
+
* 3. an exact entry in CONTEXT_WINDOW_BY_MODEL
|
|
344
|
+
* 4. a family entry, by dropping trailing '-' segments off the id
|
|
345
|
+
* 5. the platform default
|
|
346
|
+
*
|
|
347
|
+
* Step 4 is why a new or suffixed id no longer drops straight to the platform
|
|
348
|
+
* default: 'gpt-5.6-luna' resolves via 'gpt-5.6', and a dated Claude snapshot
|
|
349
|
+
* such as 'claude-opus-4-7-20260101' resolves via 'claude-opus-4-7'. The walk
|
|
350
|
+
* stops at the first hit, so a more specific entry always wins over its family.
|
|
351
|
+
*/
|
|
352
|
+
declare function getContextWindow(platform: string, model?: string, serviceId?: string): number;
|
|
327
353
|
declare function stripFileBlocksFromHistory(content: string): string;
|
|
328
354
|
type BoundedChatOptions = {
|
|
329
355
|
platform: string;
|
|
@@ -423,6 +449,21 @@ declare function isHttpUrlLike(target: string): boolean;
|
|
|
423
449
|
* space in it, and deleting it points at a file that does not exist.
|
|
424
450
|
*/
|
|
425
451
|
declare function repairUrlWhitespace(href: string): string;
|
|
452
|
+
/**
|
|
453
|
+
* A model reproducing a URL sometimes HTML-escapes its `&` query separators as
|
|
454
|
+
* `&` (or the numeric `&` / `&`). Left in the href that escaping
|
|
455
|
+
* survives the client's own escapeAttr -> v-html/innerHTML decode round-trip and
|
|
456
|
+
* reaches the browser LITERALLY, so a presigned S3 URL is navigated with its
|
|
457
|
+
* parameters named `amp;Signature`, `amp;Expires`, `amp;response-content-type`,
|
|
458
|
+
* ... — the real params vanish, the signature can't be located, and S3 rejects
|
|
459
|
+
* it (the "링크가 안되" dead export link). Undo just that entity escaping.
|
|
460
|
+
*
|
|
461
|
+
* This is a no-op on a clean URL: a valid link carries a raw `&` between params
|
|
462
|
+
* and percent-encodes (`%26`) any literal ampersand that is data, so a real URL
|
|
463
|
+
* never contains `&` to begin with. Mirrors repairUrlWhitespace: it repairs
|
|
464
|
+
* model damage, not the URL. The loop collapses a doubly-escaped `&amp;` too.
|
|
465
|
+
*/
|
|
466
|
+
declare function repairUrlEntities(href: string): string;
|
|
426
467
|
/**
|
|
427
468
|
* Trim punctuation and unmatched wrappers that cling to a token in prose.
|
|
428
469
|
* `src::a/b.pdf).` -> `src::a/b.pdf`, while a balanced `file (v2).pdf` is kept.
|
|
@@ -466,9 +507,78 @@ declare function classifyInlineLink(full: string, groups: Array<string | undefin
|
|
|
466
507
|
} | null;
|
|
467
508
|
declare function truncateLabelForDisplay(label: string): string;
|
|
468
509
|
|
|
510
|
+
/**
|
|
511
|
+
* Chat timestamp formatting, shared so agent.vue and the widget render an
|
|
512
|
+
* identical "small text under the bubble". Pure and locale-aware: it formats a
|
|
513
|
+
* given epoch-ms value, it never reads the current time, so it stays testable and
|
|
514
|
+
* DOM-free.
|
|
515
|
+
*/
|
|
516
|
+
/** Wall-clock epoch ms. Separate from the engine's monotonic nowMs() (which is
|
|
517
|
+
* performance.now() when available and therefore NOT epoch): a displayed
|
|
518
|
+
* timestamp must be wall time. */
|
|
519
|
+
declare function wallClockNow(): number;
|
|
520
|
+
/**
|
|
521
|
+
* "Jul 24, 2026, 3:42:07 PM" (locale-formatted). Empty string for a missing or
|
|
522
|
+
* non-finite value, so a caller can gate rendering on the result being truthy and
|
|
523
|
+
* a pending bubble (no timestamp yet) simply shows nothing.
|
|
524
|
+
*/
|
|
525
|
+
declare function formatChatTimestamp(ms?: number): string;
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* The `ai_agent` service option, parsed and serialized in ONE place.
|
|
529
|
+
*
|
|
530
|
+
* Stored on the skapi service record as up to three '#'-delimited segments:
|
|
531
|
+
*
|
|
532
|
+
* none AI chat disabled
|
|
533
|
+
* claude platform chosen, no model saved yet
|
|
534
|
+
* claude#claude-sonnet-4-6 platform + model
|
|
535
|
+
* claude#claude-sonnet-4-6#400000 platform + model + context-window override
|
|
536
|
+
*
|
|
537
|
+
* The third segment is new and optional, so every value written before it
|
|
538
|
+
* existed parses unchanged with `contextWindow: null`. Nothing writes a third
|
|
539
|
+
* segment without a model, because the window is meaningless without one.
|
|
540
|
+
*
|
|
541
|
+
* This lived as four separate copies (agent.vue, dbfile.vue, service.vue, and
|
|
542
|
+
* the widget's index.js), which is how the format drifts. New callers should
|
|
543
|
+
* import from here rather than re-deriving the split.
|
|
544
|
+
*/
|
|
545
|
+
type AiAgentPlatform = 'claude' | 'openai' | null;
|
|
546
|
+
type ParsedAiAgent = {
|
|
547
|
+
/** null when unset or explicitly 'none'. */
|
|
548
|
+
platform: AiAgentPlatform;
|
|
549
|
+
/** '' when no model has been saved. */
|
|
550
|
+
model: string;
|
|
551
|
+
/** Per-project context-window override in tokens, or null to use the model's. */
|
|
552
|
+
contextWindow: number | null;
|
|
553
|
+
/** True when a real platform is configured (i.e. not unset and not 'none'). */
|
|
554
|
+
hasPlatform: boolean;
|
|
555
|
+
};
|
|
556
|
+
declare function parseAiAgentValue(value: string | null | undefined): ParsedAiAgent;
|
|
557
|
+
declare function buildAiAgentValue(platform: string | null | undefined, model?: string | null, contextWindow?: number | null): string;
|
|
558
|
+
|
|
469
559
|
declare function filterListByClearHorizon(list: any[], clearedAt: number): any[];
|
|
470
560
|
declare function normalizeTextContent(content: any): string;
|
|
471
561
|
declare function extractLastUserTextFromRequest(requestBody: any): string;
|
|
562
|
+
/** The two openings an indexing prompt can have. A bg-queue item that starts with
|
|
563
|
+
* neither is an ordinary chat that happened to be routed onto that queue. */
|
|
564
|
+
declare function isIndexingRequestText(userText: any): boolean;
|
|
565
|
+
type IndexingRequestRef = {
|
|
566
|
+
name: string;
|
|
567
|
+
path?: string;
|
|
568
|
+
mime?: string;
|
|
569
|
+
size?: number;
|
|
570
|
+
/** A CONTINUE pass rather than the run's first. */
|
|
571
|
+
continued: boolean;
|
|
572
|
+
};
|
|
573
|
+
/**
|
|
574
|
+
* The file an indexing prompt is about, read back out of the prompt itself.
|
|
575
|
+
*
|
|
576
|
+
* The prompt is the only description of the pass that survives on the server, so
|
|
577
|
+
* this is how BOTH a history rebuild and a worker-minted pass the client never
|
|
578
|
+
* dispatched (ChatSession._adoptWorkerIndexingPasses) recover the file. Shared so
|
|
579
|
+
* the two produce the same `_indexFile`, which is what makes them group together.
|
|
580
|
+
*/
|
|
581
|
+
declare function parseIndexingRequestText(userText: any): IndexingRequestRef | null;
|
|
472
582
|
type MapHistoryOptions = {
|
|
473
583
|
clearedAt: number;
|
|
474
584
|
serviceId: string;
|
|
@@ -681,11 +791,20 @@ type BgTaskEntry = {
|
|
|
681
791
|
/** How many CONTINUE passes have already run for this file (resume-across-passes). */
|
|
682
792
|
resumePass?: number;
|
|
683
793
|
};
|
|
794
|
+
/**
|
|
795
|
+
* `queue` narrows the fetch to one processing chain; `status` narrows it to items
|
|
796
|
+
* in one state. Passing both is how the client asks "is there still unresolved
|
|
797
|
+
* work on the background-indexing queue?" without pulling a page of chat history
|
|
798
|
+
* (see ChatSession._adoptWorkerIndexingPasses) — the server answers that from a
|
|
799
|
+
* status-keyed index, so the reply carries only the live items, not the bodies of
|
|
800
|
+
* everything already finished.
|
|
801
|
+
*/
|
|
684
802
|
declare function getChatHistory(params: {
|
|
685
803
|
service?: string;
|
|
686
804
|
owner?: string;
|
|
687
805
|
platform: 'claude' | 'openai';
|
|
688
806
|
queue?: string;
|
|
807
|
+
status?: 'pending' | 'running' | 'resolved' | 'failed';
|
|
689
808
|
}, fetchOptions: Record<string, any>): Promise<any>;
|
|
690
809
|
|
|
691
810
|
/**
|
|
@@ -753,6 +872,12 @@ interface ChatMessage {
|
|
|
753
872
|
_localId?: string;
|
|
754
873
|
_cancelling?: boolean;
|
|
755
874
|
_cancelError?: string;
|
|
875
|
+
/** Epoch ms shown as small text under the bubble. From the request history a
|
|
876
|
+
* USER bubble carries the request's `created` time and an ASSISTANT bubble the
|
|
877
|
+
* `updated` (response) time; a live bubble is stamped with the wall clock when
|
|
878
|
+
* it is created, then reconciled to the server value on the next history load.
|
|
879
|
+
* Absent while a turn is still pending, so no time shows on a "Thinking" bubble. */
|
|
880
|
+
_ts?: number;
|
|
756
881
|
_ownerKey?: string;
|
|
757
882
|
}
|
|
758
883
|
interface ChatState {
|
|
@@ -845,12 +970,34 @@ interface ChatHost {
|
|
|
845
970
|
* repeating forever, and any real question the user asks in between gets buried.
|
|
846
971
|
*
|
|
847
972
|
* buildChatDisplayList turns the flat message array into a DISPLAY list in which
|
|
848
|
-
* every message belonging to one file (however far apart
|
|
849
|
-
* else is interleaved between them) is represented by a single
|
|
850
|
-
* rendered at the position of that
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
973
|
+
* every message belonging to one run of one file (however far apart the passes
|
|
974
|
+
* sit, and whatever else is interleaved between them) is represented by a single
|
|
975
|
+
* group entry, rendered at the position of that run's FIRST loaded pass.
|
|
976
|
+
*
|
|
977
|
+
* First, not newest. The message array is ordered by request CREATION time, and
|
|
978
|
+
* a run's later passes are created one at a time as the previous one resolves —
|
|
979
|
+
* by the client for the paged text path, and by the WORKER itself for the
|
|
980
|
+
* rendered-page (PDF) path, which the client only learns about on its next
|
|
981
|
+
* first-page history fetch. So a pass is routinely created minutes after the
|
|
982
|
+
* upload, on a queue that runs in parallel with the foreground chat. Anchoring
|
|
983
|
+
* at the newest pass meant one such late pass dragged the whole run — every
|
|
984
|
+
* earlier pass with it — below any question the user had asked in the meantime,
|
|
985
|
+
* and made a run that had visibly finished before the question render after it.
|
|
986
|
+
* Anchoring at the first pass puts the row where the run actually began, which
|
|
987
|
+
* is a position no later pass can change:
|
|
988
|
+
* - a new pass never relocates the row, so nothing under the reader shifts;
|
|
989
|
+
* - a question asked after the upload always renders below the row, which is
|
|
990
|
+
* the order it happened in.
|
|
991
|
+
* The cost is that a long-running index does not follow the conversation to the
|
|
992
|
+
* bottom: once the user has chatted past the upload, the spinner and the Stop
|
|
993
|
+
* button sit above their newest turns. That is a scroll away, and the row no
|
|
994
|
+
* longer lies about when the work started.
|
|
995
|
+
*
|
|
996
|
+
* Paging older history is the one thing that CAN move the row: an older page
|
|
997
|
+
* carrying earlier passes of a run already on screen moves it up into that page.
|
|
998
|
+
* That happens at most once per run, only for a run whose start was never
|
|
999
|
+
* loaded (`mayHaveOlder`), and it is the same event that already re-derives
|
|
1000
|
+
* `runKey` — so the view treats it as a new row either way.
|
|
854
1001
|
*
|
|
855
1002
|
* The group deliberately reports no authoritative pass TOTAL. History is paged
|
|
856
1003
|
* newest-first, so any total computed from loaded messages is a lower bound that
|
|
@@ -873,9 +1020,10 @@ type IndexingGroup = {
|
|
|
873
1020
|
* Monday and re-indexed on Wednesday is two runs, and collapsing them into
|
|
874
1021
|
* one row erased Monday's from Monday's place in the conversation, claimed
|
|
875
1022
|
* its passes for Wednesday, and let Monday's failure be overwritten by
|
|
876
|
-
* Wednesday's success.
|
|
877
|
-
*
|
|
878
|
-
*
|
|
1023
|
+
* Wednesday's success. Named after the run's FIRST loaded pass (see where it
|
|
1024
|
+
* is assigned below), so passes appended to the run and other runs appearing
|
|
1025
|
+
* on either side of it never rename a row already on screen. This is the
|
|
1026
|
+
* render key and the expansion key. */
|
|
879
1027
|
runKey: string;
|
|
880
1028
|
name: string;
|
|
881
1029
|
path?: string;
|
|
@@ -904,8 +1052,16 @@ type IndexingGroup = {
|
|
|
904
1052
|
/** The file's first pass is not among the loaded messages, so earlier passes
|
|
905
1053
|
* exist in history that has not been paged in yet. */
|
|
906
1054
|
mayHaveOlder: boolean;
|
|
907
|
-
/** Position in the source array this collapsed row renders at
|
|
1055
|
+
/** Position in the source array this collapsed row renders at: the index of
|
|
1056
|
+
* the run's FIRST loaded pass (see the file docstring for why not the last). */
|
|
908
1057
|
anchorIndex: number;
|
|
1058
|
+
/** Identity of the turn at `anchorIndex` — its server item id, or its local id
|
|
1059
|
+
* while it has none, or `''` when it has neither. The views stamp this on the
|
|
1060
|
+
* row (`data-row-pos`) so the scroll anchor can tell a row that RELOCATED (an
|
|
1061
|
+
* older page moved the run's start) from one that merely gained a pass. They
|
|
1062
|
+
* must not re-derive it from `members`: which member the row renders at is
|
|
1063
|
+
* this module's decision, and the two silently disagreed once already. */
|
|
1064
|
+
anchorId: string;
|
|
909
1065
|
};
|
|
910
1066
|
type DisplayEntry = {
|
|
911
1067
|
kind: 'message';
|
|
@@ -1083,6 +1239,9 @@ declare class ChatSession {
|
|
|
1083
1239
|
_clearPendingUserBubble(itemId: string): void;
|
|
1084
1240
|
resumePendingRequest(token: number): Promise<void>;
|
|
1085
1241
|
handleHistoryItemResolution(itemId: string, response: any, platform: string): void;
|
|
1242
|
+
/** The file an already-rendered background pass is about, off its request
|
|
1243
|
+
* bubble. Null for an ordinary turn, which is most of them. */
|
|
1244
|
+
private _indexRefOfItem;
|
|
1086
1245
|
applyHistoryItemResolution(itemId: string, response: any, platform: string): void;
|
|
1087
1246
|
/** How a bg task maps onto a collapsed row: the row's own key (storage path
|
|
1088
1247
|
* when known, else the filename), scoped to the chat it belongs to. A storage
|
|
@@ -1114,6 +1273,49 @@ declare class ChatSession {
|
|
|
1114
1273
|
* Runs from drainBgTaskQueue, which both clients call after a history load.
|
|
1115
1274
|
*/
|
|
1116
1275
|
private _sweepCancelledIndexing;
|
|
1276
|
+
/**
|
|
1277
|
+
* True when the WORKER, not this client, drives the rest of this file's chain.
|
|
1278
|
+
* The mirror image of the early returns in maybeResumeIndexing: whatever that
|
|
1279
|
+
* refuses to continue is exactly what nothing client-side is tracking.
|
|
1280
|
+
*/
|
|
1281
|
+
private _isWorkerDrivenIndexing;
|
|
1282
|
+
/**
|
|
1283
|
+
* Pick up indexing passes the WORKER minted, which no client ever dispatched.
|
|
1284
|
+
*
|
|
1285
|
+
* For a PDF (and for text/grid when windowed indexing is on) the worker writes
|
|
1286
|
+
* pass N+1's row itself, inside pass N's invocation, right after saving pass
|
|
1287
|
+
* N's result. That row reaches this client through nothing at all: it is not in
|
|
1288
|
+
* bgTaskQueue (the client never asked for it) and it is not in state.messages
|
|
1289
|
+
* (only a first-page history load maps it in, which happens on mount, project
|
|
1290
|
+
* switch or tab return). So between two worker passes every pass the client
|
|
1291
|
+
* knows about is settled, and the collapsed row renders "Indexed N passes"
|
|
1292
|
+
* with no spinner and no Stop, for a file that is still being read. A user who
|
|
1293
|
+
* believes that then asks questions against a half-indexed file — which is what
|
|
1294
|
+
* this exists to prevent.
|
|
1295
|
+
*
|
|
1296
|
+
* So when a background indexing pass settles, ask the bg queue what is still
|
|
1297
|
+
* unresolved on it and adopt anything unknown as an ordinary BgTaskEntry.
|
|
1298
|
+
* drainBgTaskQueue then treats it exactly like a pass this client dispatched:
|
|
1299
|
+
* same bubble, same `_indexFile` (so it joins the file's collapsed row), same
|
|
1300
|
+
* poll — and that poll settling runs this again, so the chain is followed to
|
|
1301
|
+
* its end. `status`-scoped so the reply carries the live items only, never a
|
|
1302
|
+
* page of finished ones with their bodies.
|
|
1303
|
+
*
|
|
1304
|
+
* Termination: the only trigger is a pass SETTLING, which happens once per
|
|
1305
|
+
* pass. An adopted item that is still running is not re-adopted (its id is
|
|
1306
|
+
* already polled), and when the queue holds nothing unknown the chain stops on
|
|
1307
|
+
* its own. Nothing here is periodic — a timer that re-reads history is the
|
|
1308
|
+
* shape that previously looped fetchHistoryPage after an already-DONE index.
|
|
1309
|
+
*/
|
|
1310
|
+
private _adoptingWorkerPasses;
|
|
1311
|
+
private _adoptWorkerIndexingPasses;
|
|
1312
|
+
/** Any of these ids still queued or still polled, i.e. surviving work. */
|
|
1313
|
+
private _isTrackingAny;
|
|
1314
|
+
/** One live bg-queue item -> a BgTaskEntry, if it is an indexing pass this
|
|
1315
|
+
* client is not already tracking. Returns whether it was adopted. */
|
|
1316
|
+
private _adoptWorkerIndexingItem;
|
|
1317
|
+
/** Follow the chain on from a background indexing pass that just settled. */
|
|
1318
|
+
private _followWorkerIndexingChain;
|
|
1117
1319
|
/** Best-effort server-side cancel of a bg-queue item that has no bubble (so
|
|
1118
1320
|
* cancelQueuedMessage, which drives one, has nothing to act on). */
|
|
1119
1321
|
private _cancelServerItem;
|
|
@@ -1134,4 +1336,4 @@ declare class ChatSession {
|
|
|
1134
1336
|
bumpGate(): void;
|
|
1135
1337
|
}
|
|
1136
1338
|
|
|
1137
|
-
export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, type DisplayEntry, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, type PinnedDispatchContext, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAttachmentContent, parseIndexingLabel, readExpiredAttachmentHref, registerAttachmentParser, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
|
|
1339
|
+
export { type AiAgentPlatform, type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_INPUT_CAP_RATIO, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, type DisplayEntry, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_BUDGET_RATIO, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingRequestRef, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, type ParsedAiAgent, type PinnedDispatchContext, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildAiAgentValue, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, getProjectContextWindow, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isIndexingRequestText, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAiAgentValue, parseAttachmentContent, parseIndexingLabel, parseIndexingRequestText, readExpiredAttachmentHref, registerAttachmentParser, registerModelContextWindows, repairUrlEntities, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, setProjectContextWindow, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, wallClockNow };
|