bunnyquery 1.8.2 → 1.8.4
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 +38 -39
- package/bunnyquery.css +108 -2
- package/bunnyquery.js +1859 -310
- package/dist/engine.cjs +1503 -188
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +906 -37
- package/dist/engine.d.ts +906 -37
- package/dist/engine.mjs +1480 -189
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/budget.ts +11 -11
- package/src/engine/history.ts +23 -6
- package/src/engine/host.ts +77 -3
- package/src/engine/image_preview.ts +0 -0
- package/src/engine/index.ts +13 -0
- package/src/engine/indexing_groups.ts +323 -6
- package/src/engine/link_markup.ts +124 -0
- package/src/engine/links.ts +159 -26
- package/src/engine/office.ts +25 -8
- package/src/engine/prompts/chat_system_prompt.ts +24 -13
- package/src/engine/prompts/indexing_system_prompt.ts +19 -11
- package/src/engine/prompts/indexing_user_message.ts +32 -22
- package/src/engine/requests.ts +302 -14
- package/src/engine/session.ts +1424 -114
- package/src/engine/viewport_fill.ts +51 -4
- package/styles/chat.css +108 -2
package/dist/engine.d.ts
CHANGED
|
@@ -173,19 +173,19 @@ interface AttachmentFailureGroup {
|
|
|
173
173
|
declare function groupAttachmentFailures(attachments: any[]): AttachmentFailureGroup[];
|
|
174
174
|
|
|
175
175
|
/**
|
|
176
|
-
* BASE PROMPT
|
|
176
|
+
* BASE PROMPT - Chat assistant
|
|
177
177
|
* ============================================================================
|
|
178
178
|
* System prompt sent on every chat turn. Rebuilt fresh on every send because
|
|
179
179
|
* the project name/description can change at any time.
|
|
180
180
|
*
|
|
181
181
|
* The `${...}` placeholders are filled from the live project (service):
|
|
182
|
-
*
|
|
183
|
-
* serviceName
|
|
184
|
-
* serviceDescription
|
|
182
|
+
* projectId -> the project ID the assistant is scoped to
|
|
183
|
+
* serviceName -> project display name (only added if a description exists)
|
|
184
|
+
* serviceDescription -> project description (only added if present)
|
|
185
185
|
*/
|
|
186
186
|
type ChatSystemPromptParams = {
|
|
187
187
|
/** The project/service ID this assistant is scoped to (formatted form). */
|
|
188
|
-
|
|
188
|
+
projectId: string;
|
|
189
189
|
/** Project display name. Only appended when a description is also present. */
|
|
190
190
|
serviceName?: string;
|
|
191
191
|
/** Project description. When present, name + description are appended. */
|
|
@@ -194,7 +194,7 @@ type ChatSystemPromptParams = {
|
|
|
194
194
|
declare function buildChatSystemPrompt(params: ChatSystemPromptParams): string;
|
|
195
195
|
|
|
196
196
|
/**
|
|
197
|
-
* BASE PROMPT
|
|
197
|
+
* BASE PROMPT - Background file-indexing agent (system prompt)
|
|
198
198
|
* ============================================================================
|
|
199
199
|
* System prompt for the BACKGROUND indexing agent (notifyAgentSaveAttachment).
|
|
200
200
|
* Its only job is to read the freshly uploaded file and persist what it learns
|
|
@@ -202,8 +202,8 @@ declare function buildChatSystemPrompt(params: ChatSystemPromptParams): string;
|
|
|
202
202
|
* user-message template in ./indexing_user_message.ts.
|
|
203
203
|
*/
|
|
204
204
|
type IndexingSystemPromptParams = {
|
|
205
|
-
/** The project
|
|
206
|
-
|
|
205
|
+
/** The PUBLIC project ID being indexed into (formatted token; the form the MCP tools accept). */
|
|
206
|
+
projectId: string;
|
|
207
207
|
/** Project display name. Only appended when a description is also present. */
|
|
208
208
|
serviceName?: string;
|
|
209
209
|
/** Project description. When present, name + description are appended. */
|
|
@@ -212,14 +212,14 @@ type IndexingSystemPromptParams = {
|
|
|
212
212
|
declare function buildIndexingSystemPrompt(params: IndexingSystemPromptParams): string;
|
|
213
213
|
|
|
214
214
|
/**
|
|
215
|
-
* BASE PROMPT
|
|
215
|
+
* BASE PROMPT - Background file-indexing agent (user message)
|
|
216
216
|
* ============================================================================
|
|
217
217
|
* USER-role message paired with the indexing system prompt. Sent by
|
|
218
218
|
* notifyAgentSaveAttachment() each time a file is uploaded or re-indexed.
|
|
219
219
|
*
|
|
220
220
|
* NOTE: the leading line "A new file has just been uploaded. Index it now." and
|
|
221
221
|
* the "- name: ..." line are also what the chat client parses to build the
|
|
222
|
-
* "Indexing: <name>" history bubble
|
|
222
|
+
* "Indexing: <name>" history bubble - keep those fields on their own lines.
|
|
223
223
|
*/
|
|
224
224
|
type IndexingAttachmentInfo = {
|
|
225
225
|
/** Original file name. */
|
|
@@ -235,15 +235,15 @@ type IndexingAttachmentInfo = {
|
|
|
235
235
|
};
|
|
236
236
|
type BuildIndexingUserMessageOptions = {
|
|
237
237
|
/**
|
|
238
|
-
* For
|
|
238
|
+
* For files with no paged reader (.epub/.hwp/.doc/.rtf, source code) the model can't read the binary via
|
|
239
239
|
* web_fetch, so the proxy worker extracts the text server-side and replaces
|
|
240
240
|
* this exact token with it. When provided, the message embeds the token (and
|
|
241
|
-
* drops the temporary-URL line
|
|
241
|
+
* drops the temporary-URL line - there is nothing for the model to fetch).
|
|
242
242
|
*/
|
|
243
243
|
inlineContentPlaceholder?: string;
|
|
244
244
|
/**
|
|
245
245
|
* Actual file content parsed CLIENT-SIDE by an attachment-parser plugin (e.g.
|
|
246
|
-
* an .hwp parser). Embedded inline verbatim
|
|
246
|
+
* an .hwp parser). Embedded inline verbatim - no server extraction and no
|
|
247
247
|
* web_fetch for this file. Takes precedence over `inlineContentPlaceholder`.
|
|
248
248
|
*/
|
|
249
249
|
inlineContent?: string;
|
|
@@ -321,8 +321,8 @@ declare function registerModelContextWindows(models: Array<{
|
|
|
321
321
|
id?: string;
|
|
322
322
|
max_input_tokens?: number;
|
|
323
323
|
}> | null | undefined): void;
|
|
324
|
-
declare function setProjectContextWindow(
|
|
325
|
-
declare function getProjectContextWindow(
|
|
324
|
+
declare function setProjectContextWindow(projectId: string, tokens: number | null | undefined): void;
|
|
325
|
+
declare function getProjectContextWindow(projectId: string): number | null;
|
|
326
326
|
declare var OUTPUT_TOKEN_RESERVE: number;
|
|
327
327
|
declare var TOOL_AND_RESPONSE_BUFFER: number;
|
|
328
328
|
declare var MIN_INPUT_TOKEN_BUDGET: number;
|
|
@@ -349,7 +349,7 @@ declare function estimateMessageTokens(msg: {
|
|
|
349
349
|
* such as 'claude-opus-4-7-20260101' resolves via 'claude-opus-4-7'. The walk
|
|
350
350
|
* stops at the first hit, so a more specific entry always wins over its family.
|
|
351
351
|
*/
|
|
352
|
-
declare function getContextWindow(platform: string, model?: string,
|
|
352
|
+
declare function getContextWindow(platform: string, model?: string, projectId?: string): number;
|
|
353
353
|
declare function stripFileBlocksFromHistory(content: string): string;
|
|
354
354
|
type BoundedChatOptions = {
|
|
355
355
|
platform: string;
|
|
@@ -360,7 +360,7 @@ type BoundedChatOptions = {
|
|
|
360
360
|
content: string;
|
|
361
361
|
}>;
|
|
362
362
|
/** Used to strip/rewrite expired attachment links in older user turns. */
|
|
363
|
-
|
|
363
|
+
projectId: string;
|
|
364
364
|
};
|
|
365
365
|
declare function buildBoundedChatMessages(options: BoundedChatOptions): {
|
|
366
366
|
messages: {
|
|
@@ -470,7 +470,7 @@ declare function prepareDownloadText(filename: string, body: string): {
|
|
|
470
470
|
|
|
471
471
|
/**
|
|
472
472
|
* Pure link/path helpers (no DOM, no marked). Moved verbatim from the chatbox.
|
|
473
|
-
* `
|
|
473
|
+
* `projectId` is passed as a PARAMETER (the original read it from a global) so
|
|
474
474
|
* the engine stays consumer-agnostic. The HTML-emitting helpers
|
|
475
475
|
* (buildLinkPartFromGroups, linkToAnchorHtml, fileToAnchorHtml, parseMsgParts*)
|
|
476
476
|
* stay in each VIEW — only these pure pieces move here.
|
|
@@ -488,6 +488,33 @@ declare var LINK_LABEL_MAX_DISPLAY_CHARS: number;
|
|
|
488
488
|
* not, which is precisely the kind of divergence a shared constant exists to stop.
|
|
489
489
|
*/
|
|
490
490
|
declare var EXPIRED_LINK_REFRESH_EXPIRES_SECONDS: number;
|
|
491
|
+
/**
|
|
492
|
+
* Seconds the browser may reuse a minted preview url (`browser_cache`).
|
|
493
|
+
*
|
|
494
|
+
* A presigned url is a fresh SigV4 query string on every mint, so it can never
|
|
495
|
+
* be a browser cache key on its own and every reload re-downloads every image.
|
|
496
|
+
* Asking for the MINT with a cacheable GET fixes it from the other end: the same
|
|
497
|
+
* url comes back out of the browser cache, so the body already on disk stays
|
|
498
|
+
* addressable.
|
|
499
|
+
*
|
|
500
|
+
* Deliberately far longer than EXPIRED_LINK_REFRESH_EXPIRES_SECONDS above, and
|
|
501
|
+
* that is the whole trick: the url is short-lived while the file stays available
|
|
502
|
+
* locally for a WEEK. What keeps an image painting is the cached BODY, not a live
|
|
503
|
+
* url. Once the browser evicts that body it refetches with a url that has since
|
|
504
|
+
* expired, gets a 403, and the error path re-mints with `refresh`. That path is
|
|
505
|
+
* therefore load-bearing, not a rare fallback.
|
|
506
|
+
*
|
|
507
|
+
* A week is the platform default for reading a private file, not a number chosen
|
|
508
|
+
* here: skapi-js reads every private record file with
|
|
509
|
+
* PRIVATE_FILE_BROWSER_CACHE_SECONDS = 7 days against the same 20-minute url, and
|
|
510
|
+
* get_signed_url caps the header at BROWSER_CACHE_MAX_SECONDS = 7 days. A chat
|
|
511
|
+
* that asked for a day was re-downloading images the rest of the product would
|
|
512
|
+
* have served from disk.
|
|
513
|
+
*
|
|
514
|
+
* Applies to previews only. A CLICK must open a live url, so the chip refresh
|
|
515
|
+
* stays on an uncached POST mint.
|
|
516
|
+
*/
|
|
517
|
+
declare var PREVIEW_BROWSER_CACHE_SECONDS: number;
|
|
491
518
|
/**
|
|
492
519
|
* How long a client may keep serving an href it already minted before dropping
|
|
493
520
|
* back to the placeholder and re-minting.
|
|
@@ -502,10 +529,10 @@ declare function createInlineLinkRegex(): RegExp;
|
|
|
502
529
|
declare function safeDecodeURIComponent(v: string): string;
|
|
503
530
|
declare function encodePathSegments(path: string): string;
|
|
504
531
|
declare function normalizeAttachmentPathCandidate(value: string): string;
|
|
505
|
-
declare function extractRemotePathFromAttachmentHref(href: string,
|
|
532
|
+
declare function extractRemotePathFromAttachmentHref(href: string, projectId: string): string | null;
|
|
506
533
|
declare function getExpiredAttachmentVisiblePath(remotePath: string, fallback?: string): string;
|
|
507
534
|
declare function buildDisplayExpiredAttachmentHref(remotePath: string, fallback?: string): string;
|
|
508
|
-
declare function isServiceDbAttachmentHref(href: string,
|
|
535
|
+
declare function isServiceDbAttachmentHref(href: string, projectId: string): boolean;
|
|
509
536
|
/**
|
|
510
537
|
* Read the storage path back out of an `_expired_.url` placeholder.
|
|
511
538
|
*
|
|
@@ -515,7 +542,7 @@ declare function isServiceDbAttachmentHref(href: string, serviceId: string): boo
|
|
|
515
542
|
* way back in. Returns null for anything that is not the carrier.
|
|
516
543
|
*/
|
|
517
544
|
declare function readExpiredAttachmentHref(href: string): string | null;
|
|
518
|
-
declare function sanitizeAttachmentLinksForHistory(content: string,
|
|
545
|
+
declare function sanitizeAttachmentLinksForHistory(content: string, projectId: string, forAssistant?: boolean): string;
|
|
519
546
|
/**
|
|
520
547
|
* Is this markdown link target a URL rather than a db storage path?
|
|
521
548
|
*
|
|
@@ -565,6 +592,33 @@ declare function repairUrlEntities(href: string): string;
|
|
|
565
592
|
* `src::a/b.pdf).` -> `src::a/b.pdf`, while a balanced `file (v2).pdf` is kept.
|
|
566
593
|
*/
|
|
567
594
|
declare function normalizeTrailingInlineToken(value: string): string;
|
|
595
|
+
/**
|
|
596
|
+
* Extensions a BROWSER can paint in an <img>, mapped to the content type the
|
|
597
|
+
* presign must declare.
|
|
598
|
+
*
|
|
599
|
+
* The content type is not optional here. get_signed_url only sets
|
|
600
|
+
* ResponseContentType when the caller passes `contentType`, and otherwise falls
|
|
601
|
+
* back to application/octet-stream, which a new tab DOWNLOADS instead of
|
|
602
|
+
* displaying. Since the whole point of the preview is that clicking it shows the
|
|
603
|
+
* picture, the mint has to name the real type.
|
|
604
|
+
*
|
|
605
|
+
* Deliberately narrower than the extraction/vision lists elsewhere in the repo:
|
|
606
|
+
* heic/heif out: Safari paints them, Chrome and Firefox show a broken image,
|
|
607
|
+
* and it is the format every iPhone photo arrives in, so the
|
|
608
|
+
* failure would be common and would read as a bug.
|
|
609
|
+
* tif/wmf/emf out: no mainstream browser paints them.
|
|
610
|
+
* svg out: inside an <img> an SVG is script-disabled and safe, but this
|
|
611
|
+
* feature's click target is a TOP-LEVEL navigation, where an
|
|
612
|
+
* SVG executes its own <script> in the serving origin with that
|
|
613
|
+
* origin's cookies, from user-uploaded content. A preview is an
|
|
614
|
+
* invitation to click exactly that.
|
|
615
|
+
*/
|
|
616
|
+
declare var PREVIEWABLE_IMAGE_CONTENT_TYPES: Record<string, string>;
|
|
617
|
+
/** Extension of a path or url, query and fragment stripped, '' when none. */
|
|
618
|
+
declare function previewableExtOf(nameOrPath: string | null | undefined): string;
|
|
619
|
+
declare function isPreviewableImagePath(nameOrPath: string | null | undefined): boolean;
|
|
620
|
+
/** Content type to hand the presign so a new tab displays rather than downloads. */
|
|
621
|
+
declare function previewImageContentType(nameOrPath: string | null | undefined): string | null;
|
|
568
622
|
/** A link the view renders. `expired` means the href is the `_expired_.url`
|
|
569
623
|
* placeholder and a click must mint a fresh one from `remotePath`. */
|
|
570
624
|
interface InlineLinkPart {
|
|
@@ -575,10 +629,19 @@ interface InlineLinkPart {
|
|
|
575
629
|
expired: boolean;
|
|
576
630
|
expiredHref?: string;
|
|
577
631
|
remotePath?: string;
|
|
632
|
+
/**
|
|
633
|
+
* Set only for a file WE host whose PATH says a browser can paint it. Its
|
|
634
|
+
* presence IS the "render a preview" decision, so a view never re-tests the
|
|
635
|
+
* label and never tests `href` (which is the _expired_.url placeholder).
|
|
636
|
+
*/
|
|
637
|
+
image?: {
|
|
638
|
+
ext: string;
|
|
639
|
+
contentType: string;
|
|
640
|
+
};
|
|
578
641
|
}
|
|
579
642
|
interface InlineLinkContext {
|
|
580
643
|
/** Current project id: the leading segment to strip off a db url. */
|
|
581
|
-
|
|
644
|
+
projectId: string;
|
|
582
645
|
/** `https://db.<hostDomain>` for this deployment. */
|
|
583
646
|
dbHostPrefix: string;
|
|
584
647
|
/** A fresh url already minted for this placeholder, if the view cached one. */
|
|
@@ -601,8 +664,182 @@ declare function classifyInlineLink(full: string, groups: Array<string | undefin
|
|
|
601
664
|
part: InlineLinkPart;
|
|
602
665
|
tail?: string;
|
|
603
666
|
} | null;
|
|
667
|
+
/**
|
|
668
|
+
* "We asked for a url for this file and did not get one."
|
|
669
|
+
*
|
|
670
|
+
* A chip the client cannot mint a url for is not a link: the ↗ is a promise it
|
|
671
|
+
* already knows it cannot keep, and clicking it opens a dead tab or nothing at
|
|
672
|
+
* all. Both views therefore keep a map of failures and render those chips
|
|
673
|
+
* unavailable (renderInlineLinkHtml's `unavailable` option): greyed, ✕ instead
|
|
674
|
+
* of ↗, no href.
|
|
675
|
+
*
|
|
676
|
+
* The MAP lives in the view (agent.vue has to re-render when it changes, and
|
|
677
|
+
* that means a ref), so only the keys are here. A failure is reported with
|
|
678
|
+
* exactly one identifier (an image preview knows the storage path, a click knows
|
|
679
|
+
* the placeholder href), so marking writes one key and the lookup tries all of
|
|
680
|
+
* them.
|
|
681
|
+
*/
|
|
682
|
+
declare function linkUnavailableKeyForPath(remotePath: string): string;
|
|
683
|
+
declare function linkUnavailableKeyForHref(href: string): string;
|
|
684
|
+
declare function isLinkUnavailable(link: {
|
|
685
|
+
href?: string;
|
|
686
|
+
expiredHref?: string;
|
|
687
|
+
remotePath?: string;
|
|
688
|
+
} | null | undefined, map: Record<string, boolean | undefined> | null | undefined): boolean;
|
|
604
689
|
declare function truncateLabelForDisplay(label: string): string;
|
|
605
690
|
|
|
691
|
+
/**
|
|
692
|
+
* The chip / preview markup for one classified inline link.
|
|
693
|
+
*
|
|
694
|
+
* `classifyInlineLink` was consolidated into the engine because deciding what a
|
|
695
|
+
* link IS had drifted between the two clients and every link bug had to be found
|
|
696
|
+
* and fixed twice. The EMITTER stayed forked, byte for byte identical in
|
|
697
|
+
* agent.vue and the widget. The image preview is the first behaviour that would
|
|
698
|
+
* have had to be written twice, so the emitter moves here too.
|
|
699
|
+
*
|
|
700
|
+
* Pure string in, pure string out: no DOM, no globals, nothing reactive. That is
|
|
701
|
+
* what lets agent.vue keep memoizing parseMsgParts on the message text alone.
|
|
702
|
+
*/
|
|
703
|
+
/** Neither client sanitizes bubble HTML (no DOMPurify, no marked sanitize), so
|
|
704
|
+
* everything interpolated here is escaped at the point of interpolation. */
|
|
705
|
+
declare function escapeInlineHtml(v: string | null | undefined): string;
|
|
706
|
+
/**
|
|
707
|
+
* Previews per MESSAGE. Each one costs a presign call and an image download the
|
|
708
|
+
* moment it is hydrated, and a reply listing a folder can name dozens. Past this
|
|
709
|
+
* many the link renders as the ordinary text chip.
|
|
710
|
+
*/
|
|
711
|
+
declare var IMAGE_PREVIEWS_PER_MESSAGE: number;
|
|
712
|
+
/**
|
|
713
|
+
* The glyph IS the promise: ↗ says "click this and the file opens". When the
|
|
714
|
+
* client could not get a url for the file, keeping that glyph on a chip it knows
|
|
715
|
+
* is dead is the bug: the click either opens a tab on a 403/404 or, once the
|
|
716
|
+
* href is gone, does nothing at all with no explanation. ✕ says what happened.
|
|
717
|
+
*/
|
|
718
|
+
declare var INLINE_LINK_GLYPH: string;
|
|
719
|
+
declare var INLINE_LINK_UNAVAILABLE_GLYPH: string;
|
|
720
|
+
declare var INLINE_LINK_UNAVAILABLE_SUFFIX: string;
|
|
721
|
+
/** Widened so each client's local link-part type is assignable. */
|
|
722
|
+
interface RenderableInlineLink {
|
|
723
|
+
label: string;
|
|
724
|
+
fullLabel?: string;
|
|
725
|
+
href: string;
|
|
726
|
+
expired: boolean;
|
|
727
|
+
expiredHref?: string;
|
|
728
|
+
remotePath?: string;
|
|
729
|
+
image?: {
|
|
730
|
+
ext: string;
|
|
731
|
+
contentType: string;
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
interface InlineLinkMarkupOptions {
|
|
735
|
+
/** The view's own "a mint is in flight for this href" flag. */
|
|
736
|
+
refreshing?: boolean;
|
|
737
|
+
/** False once the caller has spent its per-message preview budget. */
|
|
738
|
+
allowImagePreview?: boolean;
|
|
739
|
+
/**
|
|
740
|
+
* The view already tried to get a url for this file and failed (see
|
|
741
|
+
* isLinkUnavailable). Renders a dead chip: ✕, greyed, no href.
|
|
742
|
+
*/
|
|
743
|
+
unavailable?: boolean;
|
|
744
|
+
}
|
|
745
|
+
declare function renderInlineLinkHtml(link: RenderableInlineLink, opts?: InlineLinkMarkupOptions): string;
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Give a rendered preview <img> a real src.
|
|
749
|
+
*
|
|
750
|
+
* Why this is not part of the parse: every stored file classifies as
|
|
751
|
+
* `expired: true` with the _expired_.url placeholder as its href, so the url an
|
|
752
|
+
* <img> needs does not exist until something mints it. agent.vue memoizes its
|
|
753
|
+
* parse on the raw message text and drops that cache whenever its link maps
|
|
754
|
+
* change, so minting THROUGH those maps would clear the whole cache once per
|
|
755
|
+
* image and re-run marked over the entire conversation each time. The mint
|
|
756
|
+
* therefore lives here, in a cache that is not reactive and is never read by the
|
|
757
|
+
* parse, and the src is written straight onto the element after render.
|
|
758
|
+
*
|
|
759
|
+
* DOM-free like the rest of the engine: the element type is structural, so a
|
|
760
|
+
* real HTMLImageElement satisfies it while this file imports nothing from
|
|
761
|
+
* lib.dom and touches no global.
|
|
762
|
+
*/
|
|
763
|
+
interface PreviewImageEl {
|
|
764
|
+
getAttribute(name: string): string | null;
|
|
765
|
+
setAttribute(name: string, value: string): void;
|
|
766
|
+
removeAttribute(name: string): void;
|
|
767
|
+
addEventListener(type: string, cb: () => void): void;
|
|
768
|
+
}
|
|
769
|
+
interface ImagePreviewContext {
|
|
770
|
+
/** Project id. Namespaces the cache, see clearImagePreviewCache. */
|
|
771
|
+
scope: string;
|
|
772
|
+
/**
|
|
773
|
+
* Mint a directly loadable url for a storage path.
|
|
774
|
+
*
|
|
775
|
+
* `contentType` is not advisory. get_signed_url only sets
|
|
776
|
+
* ResponseContentType when the caller passes one and otherwise falls back to
|
|
777
|
+
* application/octet-stream, which a new tab DOWNLOADS instead of displaying.
|
|
778
|
+
* An implementation that drops this argument still paints the preview (an
|
|
779
|
+
* <img> sniffs the bytes) but silently breaks the click.
|
|
780
|
+
*
|
|
781
|
+
* `refresh` asks for a url the browser cache cannot answer. The mint request
|
|
782
|
+
* itself is cacheable (that is what stops a reload re-downloading every
|
|
783
|
+
* image), so a plain re-mint can hand back the very url that just failed;
|
|
784
|
+
* refresh is how the caller escapes its own cache.
|
|
785
|
+
*/
|
|
786
|
+
mint: (remotePath: string, contentType: string, refresh?: boolean) => Promise<string>;
|
|
787
|
+
/** An image finished painting. Views use it to re-pin the scroll. */
|
|
788
|
+
onLoad?: (remotePath: string) => void;
|
|
789
|
+
/** A preview gave up. The caption chip is now the whole answer. */
|
|
790
|
+
onError?: (remotePath: string, err: unknown) => void;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Drop cached preview urls, for one project or all of them.
|
|
794
|
+
*
|
|
795
|
+
* The key carries the project because an identity-blind cache is how one
|
|
796
|
+
* project's content has reached another project's chat before. Call on project
|
|
797
|
+
* switch and on sign-out.
|
|
798
|
+
*/
|
|
799
|
+
declare function clearImagePreviewCache(scope?: string): void;
|
|
800
|
+
/**
|
|
801
|
+
* A url we already hold that is still comfortably alive, or null.
|
|
802
|
+
*
|
|
803
|
+
* Synchronous on purpose. Both views rebuild bubble DOM constantly (the widget
|
|
804
|
+
* tears down and re-creates every node in renderMessages; Vue re-patches v-html
|
|
805
|
+
* whenever the string changes), so an async-only assignment would blank every
|
|
806
|
+
* visible image for a frame on each re-render. Called before paint, this makes a
|
|
807
|
+
* re-render invisible.
|
|
808
|
+
*/
|
|
809
|
+
declare function peekImagePreviewUrl(ctx: ImagePreviewContext, remotePath: string): string | null;
|
|
810
|
+
/**
|
|
811
|
+
* Deduped, TTL-bounded mint for one path.
|
|
812
|
+
*
|
|
813
|
+
* `refresh` skips every cache in the way: this module's in-memory one, and the
|
|
814
|
+
* browser's cache of the mint request itself. It is what the error path uses to
|
|
815
|
+
* replace a url that has expired, and what a view would call to pick up a file
|
|
816
|
+
* that changed.
|
|
817
|
+
*/
|
|
818
|
+
declare function resolveImagePreviewUrl(ctx: ImagePreviewContext, remotePath: string, contentType: string, refresh?: boolean): Promise<string>;
|
|
819
|
+
/**
|
|
820
|
+
* Declare a stored file changed, so the next mint for it goes to the network.
|
|
821
|
+
*
|
|
822
|
+
* The mint request is browser-cached, which is what stops a reload re-downloading
|
|
823
|
+
* every image, but it also means an OVERWRITE is invisible: the file is re-posted
|
|
824
|
+
* to the same storage path, so the mint url is byte-identical, so the browser
|
|
825
|
+
* replays the cached mint and hands back the same signed url and the same cached
|
|
826
|
+
* body. The preview would keep painting the previous picture for a day.
|
|
827
|
+
*
|
|
828
|
+
* Upload paths call this because they are the one place that KNOWS the bytes
|
|
829
|
+
* changed. It is a marker, not a fetch: the refresh happens on the next mint,
|
|
830
|
+
* which is when the new bubble actually renders. In memory only, deliberately.
|
|
831
|
+
*/
|
|
832
|
+
declare function markImagePreviewStale(scope: string, remotePath: string): void;
|
|
833
|
+
/**
|
|
834
|
+
* Hydrate every un-claimed preview <img> in the given list.
|
|
835
|
+
*
|
|
836
|
+
* Idempotent: an element is claimed by its own data-bq-img-state, so a full
|
|
837
|
+
* re-render, a Vue patch, or two calls in one frame cannot mint twice. The
|
|
838
|
+
* caller decides WHICH elements to pass, so a view can move from
|
|
839
|
+
* querySelectorAll to an IntersectionObserver without an engine change.
|
|
840
|
+
*/
|
|
841
|
+
declare function hydrateImagePreviews(imgs: ArrayLike<PreviewImageEl>, ctx: ImagePreviewContext): void;
|
|
842
|
+
|
|
606
843
|
/**
|
|
607
844
|
* Chat timestamp formatting, shared so agent.vue and the widget render an
|
|
608
845
|
* identical "small text under the bubble". Pure and locale-aware: it formats a
|
|
@@ -677,7 +914,7 @@ type IndexingRequestRef = {
|
|
|
677
914
|
declare function parseIndexingRequestText(userText: any): IndexingRequestRef | null;
|
|
678
915
|
type MapHistoryOptions = {
|
|
679
916
|
clearedAt: number;
|
|
680
|
-
|
|
917
|
+
projectId: string;
|
|
681
918
|
/** View-side display formatter for "Indexing:/Reindexing: …" bubbles. */
|
|
682
919
|
formatIndexingLabel: (name: string, mime?: string, size?: number | null, storagePath?: string, reindex?: boolean, continued?: boolean) => string;
|
|
683
920
|
};
|
|
@@ -772,7 +1009,29 @@ declare function fillHistoryViewport(opts: FillHistoryViewportOptions): Promise<
|
|
|
772
1009
|
* the loop then keeps paging until EVERY caller is satisfied. Predicates that
|
|
773
1010
|
* come true are dropped as it goes, so the cost stays flat.
|
|
774
1011
|
*/
|
|
775
|
-
declare function createHistoryFiller(base: Omit<FillHistoryViewportOptions, 'isSatisfied'>
|
|
1012
|
+
declare function createHistoryFiller(base: Omit<FillHistoryViewportOptions, 'isSatisfied'> & {
|
|
1013
|
+
/** Fired when the loop starts FETCHING and when it stops, and only on a real
|
|
1014
|
+
* change.
|
|
1015
|
+
*
|
|
1016
|
+
* This — not the caller's own per-request `isLoading` — is what "older
|
|
1017
|
+
* history is still coming in" means to a view. A fill is many pages, and
|
|
1018
|
+
* `isLoading` drops to false between every one of them, so anything
|
|
1019
|
+
* rendered off it flickers once per page for the whole loop. A collapsed
|
|
1020
|
+
* indexing row whose run begins above the loaded window renders exactly
|
|
1021
|
+
* that ("still loading this run" vs a status it cannot know yet), which is
|
|
1022
|
+
* why the loop has to publish its own span.
|
|
1023
|
+
*
|
|
1024
|
+
* Fetching, NOT requested. Most fills fetch nothing: they are fired on every
|
|
1025
|
+
* window resize, every row a user collapses, and every first-page load, and
|
|
1026
|
+
* the overwhelmingly common outcome is `isSatisfied` returning true on the
|
|
1027
|
+
* first look. Announcing at request time published a true/false pair for
|
|
1028
|
+
* each of those, and the widget's own satisfied-check spans two animation
|
|
1029
|
+
* frames — long enough for the browser to PAINT the intermediate state. Every
|
|
1030
|
+
* collapsed row strobed through "loading" on every resize tick. So the span
|
|
1031
|
+
* opens at the first actual page request, which is also the first moment the
|
|
1032
|
+
* claim is true. */
|
|
1033
|
+
onRunningChange?: (running: boolean) => void;
|
|
1034
|
+
}): {
|
|
776
1035
|
fill: (isSatisfied: () => boolean | Promise<boolean>) => Promise<void>;
|
|
777
1036
|
isRunning: () => boolean;
|
|
778
1037
|
};
|
|
@@ -780,6 +1039,33 @@ declare function createHistoryFiller(base: Omit<FillHistoryViewportOptions, 'isS
|
|
|
780
1039
|
declare const MCP_NAME = "BunnyQuery";
|
|
781
1040
|
declare const DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
782
1041
|
declare const DEFAULT_OPENAI_MODEL = "gpt-5.6-luna";
|
|
1042
|
+
/** How a given model should be shown a rendered document. */
|
|
1043
|
+
type VisionProfile = {
|
|
1044
|
+
/** Per-image `detail` (OpenAI only). */
|
|
1045
|
+
detail: string;
|
|
1046
|
+
/** Pages the worker renders into one window. */
|
|
1047
|
+
pagesPerWindow: number;
|
|
1048
|
+
/** Horizontal bands per page; 1 renders the whole page as one image. */
|
|
1049
|
+
tile: number;
|
|
1050
|
+
};
|
|
1051
|
+
/**
|
|
1052
|
+
* Resolve the render profile for a model.
|
|
1053
|
+
*
|
|
1054
|
+
* Three tiers, and they fail for different reasons, which is why one set of knobs cannot
|
|
1055
|
+
* serve all of them:
|
|
1056
|
+
* - full: everything ABOVE gpt-5.4-nano - the base models, mini, and every gpt-5.6
|
|
1057
|
+
* including 5.6-nano. These already transcribe dense scans correctly, so they get
|
|
1058
|
+
* 'original' detail and are otherwise untouched.
|
|
1059
|
+
* - gpt-5.4-nano: also gets 'original', so it sees exactly the SAME pixels as the full
|
|
1060
|
+
* tier. Its gap therefore is not resolution, and tiling would do nothing for it. What
|
|
1061
|
+
* it lacks is room: a smaller window leaves the same output budget divided between
|
|
1062
|
+
* fewer pages.
|
|
1063
|
+
* - downsampled (below the 'original' floor: gpt-5.3-nano, gpt-5-nano, gpt-4.1-nano):
|
|
1064
|
+
* capped at 'high', which resamples the page onto a 512px grid no matter what DPI it
|
|
1065
|
+
* was rendered at. Render resolution is wasted on these entirely; the only way to give
|
|
1066
|
+
* them readable text is to make each image cover less of the page, which is `tile`.
|
|
1067
|
+
*/
|
|
1068
|
+
declare function getVisionProfile(model?: string): VisionProfile;
|
|
783
1069
|
type ClaudeRole = 'user' | 'assistant';
|
|
784
1070
|
type ClaudeMessage = {
|
|
785
1071
|
role: ClaudeRole;
|
|
@@ -818,6 +1104,7 @@ type CallClaudeWithMcpParams = {
|
|
|
818
1104
|
onError?: (err: any) => void;
|
|
819
1105
|
};
|
|
820
1106
|
declare const POLL_INTERVAL = 3000;
|
|
1107
|
+
declare const MAX_CONCURRENT_BG_POLLS = 6;
|
|
821
1108
|
declare function callClaudeWithMcp({ prompt, messages, service, owner, userId, model, maxTokens, system, mcpServer, extractContent, fileUrls, }: CallClaudeWithMcpParams): Promise<any>;
|
|
822
1109
|
declare function callClaudeWithPublicMcp(prompt: string, service: string, owner: string, messages?: ClaudeMessage[], system?: string, model?: string, userId?: string, extractContent?: ExtractDirective[], fileUrls?: FileUrlDirective[], onResponse?: (res: any) => void, onError?: (err: any) => void): Promise<any>;
|
|
823
1110
|
declare function callOpenAIWithPublicMcp(prompt: string, service: string, owner: string, messages?: OpenAIMessage[], system?: string, model?: string, userId?: string, extractContent?: ExtractDirective[], fileUrls?: FileUrlDirective[], onResponse?: (res: any) => void, onError?: (err: any) => void): Promise<any>;
|
|
@@ -825,8 +1112,20 @@ type AttachmentSaveInfo = {
|
|
|
825
1112
|
platform: 'claude' | 'openai';
|
|
826
1113
|
model?: string;
|
|
827
1114
|
service: string;
|
|
1115
|
+
/** The PUBLIC project ID (formatted token, skapi.project_id). Shown to the model; falls back to `service`. */
|
|
1116
|
+
publicProjectId?: string;
|
|
828
1117
|
owner: string;
|
|
829
|
-
|
|
1118
|
+
/**
|
|
1119
|
+
* Queue base for this indexing pass: "<userId>-bg". REQUIRED, and it must be
|
|
1120
|
+
* the SAME value the chat turn uses (ChatSession.dispatchComposedMessage's
|
|
1121
|
+
* `id.userId || id.projectId`) — the backend serialises requests that share a
|
|
1122
|
+
* queue name and runs different ones IN PARALLEL, so a pass enqueued under a
|
|
1123
|
+
* different base does not hold the chat back at all. It was optional once,
|
|
1124
|
+
* defaulting to `service`; the chatbox omitted it, and its files were indexed
|
|
1125
|
+
* on "<projectId>-bg" while its question ran on "<userId>-bg" — the question
|
|
1126
|
+
* was answered from a file nothing had read yet. Pass `userId || projectId`.
|
|
1127
|
+
*/
|
|
1128
|
+
userId: string;
|
|
830
1129
|
serviceName?: string;
|
|
831
1130
|
serviceDescription?: string;
|
|
832
1131
|
attachment: {
|
|
@@ -861,6 +1160,14 @@ declare function extractOpenAIText(response: any): any;
|
|
|
861
1160
|
declare function listClaudeModels(service: string, owner: string): Promise<any>;
|
|
862
1161
|
declare function listOpenAIModels(service: string, owner: string): Promise<any>;
|
|
863
1162
|
declare const BG_INDEXING_QUEUE_SUFFIX = "-bg";
|
|
1163
|
+
/**
|
|
1164
|
+
* The one place the background-indexing queue name is spelled out. The backend
|
|
1165
|
+
* serialises requests sharing a queue name and runs different names in PARALLEL,
|
|
1166
|
+
* so every indexing pass AND the chat turn that must wait behind them have to
|
|
1167
|
+
* resolve to the identical string — see AttachmentSaveInfo.userId for what
|
|
1168
|
+
* happens when they do not.
|
|
1169
|
+
*/
|
|
1170
|
+
declare function bgIndexingQueueName(userId?: string, service?: string): string;
|
|
864
1171
|
/**
|
|
865
1172
|
* True when a request belongs to the background-indexing queue.
|
|
866
1173
|
*
|
|
@@ -872,7 +1179,7 @@ declare const BG_INDEXING_QUEUE_SUFFIX = "-bg";
|
|
|
872
1179
|
*/
|
|
873
1180
|
declare function isBgIndexingQueue(queueName?: string): boolean;
|
|
874
1181
|
type BgTaskEntry = {
|
|
875
|
-
|
|
1182
|
+
projectId: string;
|
|
876
1183
|
platform: 'claude' | 'openai';
|
|
877
1184
|
id: string;
|
|
878
1185
|
filename: string;
|
|
@@ -886,7 +1193,16 @@ type BgTaskEntry = {
|
|
|
886
1193
|
}) => Promise<any>) | undefined;
|
|
887
1194
|
/** How many CONTINUE passes have already run for this file (resume-across-passes). */
|
|
888
1195
|
resumePass?: number;
|
|
1196
|
+
/** The STAGED chat turn these files were attached to (ChatSession.stageOutgoingMessage).
|
|
1197
|
+
* drainBgTaskQueue inserts this pass's bubble directly ABOVE that turn's bubble, so the
|
|
1198
|
+
* collapsed row sits where the reader expects it — right before the message the files
|
|
1199
|
+
* came with — from the moment it appears, instead of the turn being moved down past it
|
|
1200
|
+
* once everything finishes. Absent for work with no chat turn behind it (the dbfile
|
|
1201
|
+
* page, an attachment-only send, a worker-adopted pass), which appends as before. */
|
|
1202
|
+
stageId?: string;
|
|
889
1203
|
};
|
|
1204
|
+
declare const INDEXING_COMPLETE_MARKER = "INDEXING_COMPLETE";
|
|
1205
|
+
declare const EMPTY_INDEXING_REPLY = "Finished reading this file.";
|
|
890
1206
|
/**
|
|
891
1207
|
* `queue` narrows the fetch to one processing chain; `status` narrows it to items
|
|
892
1208
|
* in one state. Passing both is how the client asks "is there still unresolved
|
|
@@ -912,9 +1228,17 @@ declare function getChatHistory(params: {
|
|
|
912
1228
|
* otherwise touch the DOM or a framework goes through a host hook.
|
|
913
1229
|
*/
|
|
914
1230
|
interface ChatIdentity {
|
|
915
|
-
|
|
1231
|
+
projectId: string;
|
|
1232
|
+
/**
|
|
1233
|
+
* The PUBLIC project ID: the formatted two-segment token (skapi.project_id).
|
|
1234
|
+
* projectId above is the RAW regional code the wire endpoints take; the public
|
|
1235
|
+
* token is what MCP tools accept and what prompts must show the model, since the
|
|
1236
|
+
* model copies it verbatim into tool calls. Optional for older hosts; prompts
|
|
1237
|
+
* fall back to the raw code when absent.
|
|
1238
|
+
*/
|
|
1239
|
+
publicProjectId?: string;
|
|
916
1240
|
owner: string;
|
|
917
|
-
/** Per-user queue name (falls back to
|
|
1241
|
+
/** Per-user queue name (falls back to projectId). */
|
|
918
1242
|
userId: string;
|
|
919
1243
|
platform: 'claude' | 'openai' | 'none';
|
|
920
1244
|
model?: string;
|
|
@@ -931,6 +1255,11 @@ interface ChatIdentity {
|
|
|
931
1255
|
interface PinnedDispatchContext {
|
|
932
1256
|
identity: ChatIdentity;
|
|
933
1257
|
systemPrompt: string;
|
|
1258
|
+
/** Id returned by stageOutgoingMessage. The turn's bubble is already on
|
|
1259
|
+
* screen (staged while its attachments upload), so dispatchComposedMessage
|
|
1260
|
+
* REPLACES that bubble in place instead of pushing a second one at the
|
|
1261
|
+
* bottom — the message keeps the position it was sent in. */
|
|
1262
|
+
stageId?: string;
|
|
934
1263
|
}
|
|
935
1264
|
/**
|
|
936
1265
|
* The file a background-indexing bubble belongs to. Stamped on the REQUEST
|
|
@@ -957,13 +1286,50 @@ interface ChatMessage {
|
|
|
957
1286
|
isPendingInProcess?: boolean;
|
|
958
1287
|
isPendingQueued?: boolean;
|
|
959
1288
|
isPendingOlder?: boolean;
|
|
1289
|
+
/** PROTOCOL flag: true from the moment a queued turn is dispatched until the
|
|
1290
|
+
* server acknowledges it. It is the token the ack's findIndex matches on, so
|
|
1291
|
+
* nothing may clear it early. It is NOT a style input — see _dimSending. */
|
|
960
1292
|
isSendingToServer?: boolean;
|
|
1293
|
+
/** PRESENTATIONAL flag: render this bubble dimmed because the turn has not been
|
|
1294
|
+
* handed over yet. Split from isSendingToServer because an ATTACHMENT turn is
|
|
1295
|
+
* un-dimmed the instant its files finish indexing, while the request itself is
|
|
1296
|
+
* still un-acked for another moment; dropping isSendingToServer to achieve that
|
|
1297
|
+
* would cost the turn its _serverItemId (the ack matches on that flag alone, and
|
|
1298
|
+
* a _useBgQueue turn is excluded from every fallback that would recover it). */
|
|
1299
|
+
_dimSending?: boolean;
|
|
961
1300
|
isCancelled?: boolean;
|
|
962
1301
|
isError?: boolean;
|
|
963
1302
|
isBackgroundTask?: boolean;
|
|
964
1303
|
/** Set on background-indexing REQUEST bubbles only (see IndexingFileRef). */
|
|
965
1304
|
_indexFile?: IndexingFileRef;
|
|
1305
|
+
/** Set on a background-indexing RESPONSE bubble whose raw answer carried the
|
|
1306
|
+
* INDEXING_COMPLETE marker. Stamped before the marker is stripped for display,
|
|
1307
|
+
* in every path that builds one (live resolution and both history mappers), so
|
|
1308
|
+
* a run reads the same before and after a reload.
|
|
1309
|
+
*
|
|
1310
|
+
* Meaningful ONLY for a client-driven chain, where it is the very signal
|
|
1311
|
+
* maybeResumeIndexing stops on. The worker-driven paths (PDF vision, windowed
|
|
1312
|
+
* reads) advance off the renderer's page count and their prompt deliberately
|
|
1313
|
+
* never asks for the marker, so a model that emits one there is guessing —
|
|
1314
|
+
* which is how an 88-page file once "finished" at page 15. */
|
|
1315
|
+
_indexComplete?: boolean;
|
|
966
1316
|
_useBgQueue?: boolean;
|
|
1317
|
+
/** Local id of a turn STAGED at Send time while its attachments upload. The
|
|
1318
|
+
* bubble exists before any server request does, so it is never matched by
|
|
1319
|
+
* _serverItemId and is never promoted/cancelled by the queue machinery —
|
|
1320
|
+
* dispatchComposedMessage consumes it (pinned.stageId) when the turn is
|
|
1321
|
+
* finally sent. Staged bubbles are deliberately kept OUT of the history
|
|
1322
|
+
* cache: an unmount kills the upload that would resolve them, so a cached
|
|
1323
|
+
* copy would replay as a bubble that uploads forever. */
|
|
1324
|
+
_stageId?: string;
|
|
1325
|
+
/** Staged-turn phase 1: its files are still uploading. Renders
|
|
1326
|
+
* "(Uploading files...)", dimmed. */
|
|
1327
|
+
isUploadingAttachments?: boolean;
|
|
1328
|
+
/** Staged-turn phase 2: the files are up and the turn is waiting for the whole
|
|
1329
|
+
* background-indexing chain behind them to finish. Renders "(Indexing files...)",
|
|
1330
|
+
* still dimmed. Cleared (with _dimSending) by markStagedMessageReady the moment
|
|
1331
|
+
* the queue drains, which is when the turn genuinely becomes "(In queue)". */
|
|
1332
|
+
isAwaitingIndexing?: boolean;
|
|
967
1333
|
_serverItemId?: string;
|
|
968
1334
|
_localId?: string;
|
|
969
1335
|
_cancelling?: boolean;
|
|
@@ -991,6 +1357,28 @@ interface ChatState {
|
|
|
991
1357
|
historyStartKeyHistory: string[];
|
|
992
1358
|
historyRequestToken: number;
|
|
993
1359
|
gateRefreshToken: number;
|
|
1360
|
+
/** Files the SERVER still has unresolved indexing work for, by the key a
|
|
1361
|
+
* collapsed row uses (storage path, else filename). Lives on the state rather
|
|
1362
|
+
* than privately so a reactive consumer re-renders when it changes. */
|
|
1363
|
+
liveIndexKeys: {
|
|
1364
|
+
[fileKey: string]: boolean;
|
|
1365
|
+
};
|
|
1366
|
+
/** Whether `liveIndexKeys` has been answered at least once for this chat. False
|
|
1367
|
+
* means "we have not found out", which the display layer reads as still
|
|
1368
|
+
* working — never as an all-clear. */
|
|
1369
|
+
liveIndexChecked: boolean;
|
|
1370
|
+
/** Server item ids of the indexing passes that existed — on the row, or on the
|
|
1371
|
+
* bg queue — when the user STOPPED that file. Two readers, one fact:
|
|
1372
|
+
* buildChatDisplayList reports the run as stopped when it holds any of them
|
|
1373
|
+
* (a stop routinely leaves no other trace), and _applyIndexCancellations
|
|
1374
|
+
* refuses to let one of them lift the stop the way a genuinely new indexing
|
|
1375
|
+
* request does. Ids, not file keys: they name the RUN that was stopped, so a
|
|
1376
|
+
* later re-index of the same file cannot inherit it. On the state, like
|
|
1377
|
+
* liveIndexKeys, so a reactive consumer re-renders the moment a stop is
|
|
1378
|
+
* recorded — a stop with nothing left to cancel changes no message at all. */
|
|
1379
|
+
stoppedIndexIds: {
|
|
1380
|
+
[serverItemId: string]: boolean;
|
|
1381
|
+
};
|
|
994
1382
|
}
|
|
995
1383
|
interface ChatHost {
|
|
996
1384
|
/** Read live (platform/model/name can change between sends). */
|
|
@@ -1044,6 +1432,16 @@ interface ChatHost {
|
|
|
1044
1432
|
* through to a plain re-index. Implementations must be best-effort (swallow
|
|
1045
1433
|
* "not found" / permission errors so indexing still proceeds). */
|
|
1046
1434
|
deleteExistingFileRecord?(storagePath: string): Promise<any>;
|
|
1435
|
+
/**
|
|
1436
|
+
* Create the file's "src::<storagePath>" record before indexing starts, so every pass has a
|
|
1437
|
+
* reference target that exists. Optional: a host without it keeps the old behaviour, where
|
|
1438
|
+
* whichever pass got there first created the record and the others hoped it had.
|
|
1439
|
+
*/
|
|
1440
|
+
ensureFileIndexRecord?(storagePath: string, meta?: {
|
|
1441
|
+
name?: string;
|
|
1442
|
+
mime?: string;
|
|
1443
|
+
size?: number;
|
|
1444
|
+
}): Promise<any>;
|
|
1047
1445
|
/** Map a relative path to the consumer's db storage key (e.g. uid-prefixed). */
|
|
1048
1446
|
storagePathFor(relPath: string): string;
|
|
1049
1447
|
getMimeType(name: string): string | null;
|
|
@@ -1101,6 +1499,13 @@ interface ChatHost {
|
|
|
1101
1499
|
* failed), how many passes are currently loaded, and `mayHaveOlder` when the
|
|
1102
1500
|
* file's first pass is not among them.
|
|
1103
1501
|
*
|
|
1502
|
+
* For the same reason it also reports NOT KNOWING. A run whose start is still
|
|
1503
|
+
* being paged in, and a worker-driven run whose queue state has not been asked
|
|
1504
|
+
* for yet, are both rows whose state is a moving target — and on a chatbox that
|
|
1505
|
+
* was just opened, that is most of them. `resolving` marks those, so the view can
|
|
1506
|
+
* say which wait it is waiting on rather than committing to "indexing" or
|
|
1507
|
+
* "indexed" on a fraction of the evidence.
|
|
1508
|
+
*
|
|
1104
1509
|
* Pure and view-agnostic: agent.vue and the BunnyQuery widget both render from
|
|
1105
1510
|
* this, so the two stay identical.
|
|
1106
1511
|
*/
|
|
@@ -1110,7 +1515,16 @@ type IndexingGroup = {
|
|
|
1110
1515
|
/** The FILE this row is about: storage path when known (a file can be
|
|
1111
1516
|
* re-uploaded under a name that already exists elsewhere), else name. Shared
|
|
1112
1517
|
* by every run of that file, and what ChatSession.cancelIndexingGroup and
|
|
1113
|
-
* _indexKeyOf match on — never use it as a render key.
|
|
1518
|
+
* _indexKeyOf match on — never use it as a render key.
|
|
1519
|
+
*
|
|
1520
|
+
* It IS the key for persistent view state, above all the expansion state. That
|
|
1521
|
+
* used to be keyed by runKey, which is renamed the moment a run's true first
|
|
1522
|
+
* pass loads (see below) — so a row the user had opened silently closed itself
|
|
1523
|
+
* mid-indexing, every time a pass arrived ahead of the earlier ones. This never
|
|
1524
|
+
* changes for the life of a file. The cost is that two runs OF THE SAME FILE
|
|
1525
|
+
* (an index and a later re-index) open and close together, which is a fair
|
|
1526
|
+
* reading of "show me this file's steps" and is not a state the user can be
|
|
1527
|
+
* surprised out of. */
|
|
1114
1528
|
key: string;
|
|
1115
1529
|
/** Identity of this ROW: one indexing RUN of that file. A file indexed on
|
|
1116
1530
|
* Monday and re-indexed on Wednesday is two runs, and collapsing them into
|
|
@@ -1118,8 +1532,13 @@ type IndexingGroup = {
|
|
|
1118
1532
|
* its passes for Wednesday, and let Monday's failure be overwritten by
|
|
1119
1533
|
* Wednesday's success. Named after the run's FIRST loaded pass (see where it
|
|
1120
1534
|
* is assigned below), so passes appended to the run and other runs appearing
|
|
1121
|
-
* on either side of it never rename a row already on screen.
|
|
1122
|
-
*
|
|
1535
|
+
* on either side of it never rename a row already on screen.
|
|
1536
|
+
*
|
|
1537
|
+
* This is the RENDER key, and only that. It is renamed when the run's true
|
|
1538
|
+
* first pass finally loads — routine while a worker-driven chain is running,
|
|
1539
|
+
* since a pass adopted from the queue can reach the client before the earlier
|
|
1540
|
+
* ones are paged in — and a rename is exactly right for a DOM key (the row did
|
|
1541
|
+
* change identity) but wrong for anything the USER set. Key that on `key`. */
|
|
1123
1542
|
runKey: string;
|
|
1124
1543
|
name: string;
|
|
1125
1544
|
path?: string;
|
|
@@ -1141,8 +1560,19 @@ type IndexingGroup = {
|
|
|
1141
1560
|
* when nothing is cancellable — a finished file, or a live pass whose server
|
|
1142
1561
|
* id has not come back yet. */
|
|
1143
1562
|
cancellableIds: string[];
|
|
1144
|
-
/**
|
|
1563
|
+
/** This row is in the middle of stopping: a cancel request is in flight for one
|
|
1564
|
+
* of its passes, or the user stopped the run and a pass is still running. Both
|
|
1565
|
+
* mean the same thing to a view — the Stop has been spent, so the button reads
|
|
1566
|
+
* "Stopping..." and is not offered again. */
|
|
1145
1567
|
cancelling: boolean;
|
|
1568
|
+
/** The user stopped this run.
|
|
1569
|
+
*
|
|
1570
|
+
* NOT the same as `status === 'cancelled'`, and the difference is the whole
|
|
1571
|
+
* reason it exists: a stop landing on a pass that is already RUNNING cannot
|
|
1572
|
+
* un-run it, so the row stays `active` until that pass settles. `status` then
|
|
1573
|
+
* describes the work (something is still running) and this describes the user's
|
|
1574
|
+
* decision (no more of it will be started). */
|
|
1575
|
+
stopped: boolean;
|
|
1146
1576
|
/** Why the last cancel attempt failed (e.g. the pass had already finished). */
|
|
1147
1577
|
cancelError?: string;
|
|
1148
1578
|
/** The file's first pass is not among the loaded messages, so earlier passes
|
|
@@ -1158,6 +1588,74 @@ type IndexingGroup = {
|
|
|
1158
1588
|
* must not re-derive it from `members`: which member the row renders at is
|
|
1159
1589
|
* this module's decision, and the two silently disagreed once already. */
|
|
1160
1590
|
anchorId: string;
|
|
1591
|
+
/** `members` minus the turns an EXPANDED row should not show: every CONTINUE
|
|
1592
|
+
* request, and the running pass's empty placeholder.
|
|
1593
|
+
*
|
|
1594
|
+
* A continuation's request bubble says "Indexing (continuing) <file>" and
|
|
1595
|
+
* nothing else — it repeats the row's own header once per pass, so a long file
|
|
1596
|
+
* read as the same line over and over with the actual findings buried between
|
|
1597
|
+
* them. The pass is still represented, by its RESPONSE. The placeholder goes
|
|
1598
|
+
* for a different reason: the row now carries one loader of its own for as long
|
|
1599
|
+
* as work remains, and two spinners in one open row is noise.
|
|
1600
|
+
*
|
|
1601
|
+
* Additive. `members` is untouched and is still what every count, status,
|
|
1602
|
+
* cancel and anchor decision reads — several of them are only correct on the
|
|
1603
|
+
* full list (a `mayHaveOlder` run's members[0] IS a continuation). */
|
|
1604
|
+
visibleMembers: {
|
|
1605
|
+
msg: ChatMessage;
|
|
1606
|
+
index: number;
|
|
1607
|
+
}[];
|
|
1608
|
+
/** Who advances this file's chain, which decides what can confirm it is over:
|
|
1609
|
+
* 'single' one pass and done; 'client' this client dispatches each CONTINUE
|
|
1610
|
+
* pass and stops on the model's completion marker; 'worker' the server advances
|
|
1611
|
+
* the loop off the renderer's page count and the client is only a spectator. */
|
|
1612
|
+
driver: 'single' | 'client' | 'worker';
|
|
1613
|
+
/** Positively established that no further indexing work will happen for this
|
|
1614
|
+
* run. NOT "the file was fully read" — a cap-out, a failure and a stop are all
|
|
1615
|
+
* finished, and the row's own status says which.
|
|
1616
|
+
*
|
|
1617
|
+
* False means "not established", which includes "still running" AND "we have
|
|
1618
|
+
* not been able to find out". The view shows a loader for both, deliberately:
|
|
1619
|
+
* the alternative default is the failure this exists to prevent, a row that
|
|
1620
|
+
* reads "Indexed" between two passes of a file still being read. */
|
|
1621
|
+
finished: boolean;
|
|
1622
|
+
/** This row cannot honestly claim a state yet, because something it is derived
|
|
1623
|
+
* FROM is still being fetched. Both `status` and `finished` are read off the
|
|
1624
|
+
* passes that happen to be LOADED, and on a freshly opened chatbox that is a
|
|
1625
|
+
* moving target: history pages newest-first, so a long run arrives as a tail
|
|
1626
|
+
* of CONTINUE passes while its beginning is still being paged in. The row was
|
|
1627
|
+
* picking a side through that window — a spinner reading "Indexing" for a file
|
|
1628
|
+
* that finished last week, or a green "Indexed" for one still being read — and
|
|
1629
|
+
* both are verdicts drawn from a fraction of the run.
|
|
1630
|
+
*
|
|
1631
|
+
* Only ever set from status 'done'. A loaded pending pass PROVES the run is
|
|
1632
|
+
* live, and an error or a stop is the newest pass's own outcome, which
|
|
1633
|
+
* newest-first paging always has in hand — none of those is a guess, and
|
|
1634
|
+
* hiding any of them behind a loader would lose something the user needs.
|
|
1635
|
+
*
|
|
1636
|
+
* For the 'history' reason this means "a fetch is IN FLIGHT", not "the picture
|
|
1637
|
+
* is incomplete". Older history is paged in by explicit triggers only (the
|
|
1638
|
+
* viewport fill, the user scrolling to the top) and nothing auto-fetches on a
|
|
1639
|
+
* row's behalf, so a run whose start is still unloaded once the paging stops
|
|
1640
|
+
* has to go back to reporting what it does know — `mayHaveOlder` and the `+` on
|
|
1641
|
+
* the pass count carry the rest. A "loading..." that never ends is the same lie
|
|
1642
|
+
* pointing the other way.
|
|
1643
|
+
*
|
|
1644
|
+
* The 'status' reason is weaker on purpose: "the queue has not answered", which
|
|
1645
|
+
* a permanently failing query never resolves. That is deliberate, because it is
|
|
1646
|
+
* the SAME question as what the row should say when it cannot find out, and
|
|
1647
|
+
* every alternative is worse: a grey clock reading "checking" claims less than
|
|
1648
|
+
* the yellow spinner reading "Indexing" that it replaced. It also self-heals in
|
|
1649
|
+
* practice — the answer is re-sought on every first-page history load and every
|
|
1650
|
+
* settling pass — and gating it on an in-flight query instead would mean
|
|
1651
|
+
* threading a second liveness flag through a retry ladder with nine exit
|
|
1652
|
+
* points, i.e. trading this for a flag that can stick in the other direction. */
|
|
1653
|
+
resolving: boolean;
|
|
1654
|
+
/** Which wait, so the row can name it instead of just spinning. 'history':
|
|
1655
|
+
* older pages are being fetched and this run's first pass is not among the
|
|
1656
|
+
* loaded ones. 'status': the queue has not yet said whether this file is still
|
|
1657
|
+
* being worked on, which is the only thing that can end a worker-driven run. */
|
|
1658
|
+
resolvingReason?: 'history' | 'status';
|
|
1161
1659
|
};
|
|
1162
1660
|
type DisplayEntry = {
|
|
1163
1661
|
kind: 'message';
|
|
@@ -1172,6 +1670,34 @@ type BuildDisplayListOptions = {
|
|
|
1172
1670
|
/** True while older history remains unpaged, which is what makes a group
|
|
1173
1671
|
* with no first pass genuinely incomplete rather than merely odd. */
|
|
1174
1672
|
hasMoreHistory?: boolean;
|
|
1673
|
+
/** An OLDER-history fetch is in flight right now — a single page, or the whole
|
|
1674
|
+
* viewport-fill loop (createHistoryFiller's onRunningChange, which spans the
|
|
1675
|
+
* pages between which a per-request flag keeps dropping to false).
|
|
1676
|
+
*
|
|
1677
|
+
* Older specifically. A first-page refresh cannot bring in a run's earlier
|
|
1678
|
+
* passes, so counting it here would flip every incomplete row to "still
|
|
1679
|
+
* loading" for the length of a poll that could never have answered it. */
|
|
1680
|
+
loadingOlderHistory?: boolean;
|
|
1681
|
+
/** Files the SERVER still has unresolved indexing work for, keyed exactly like
|
|
1682
|
+
* IndexingGroup.key (ChatSession.getLiveIndexState). */
|
|
1683
|
+
liveIndexKeys?: {
|
|
1684
|
+
[fileKey: string]: boolean;
|
|
1685
|
+
};
|
|
1686
|
+
/** Whether `liveIndexKeys` has been answered at least once for this chat. False
|
|
1687
|
+
* is "we do not know", and a worker-driven run stays unfinished on it. */
|
|
1688
|
+
liveIndexChecked?: boolean;
|
|
1689
|
+
/** Server item ids of passes that were on a row when the user STOPPED it
|
|
1690
|
+
* (ChatSession.state.stoppedIndexIds). A run holding any of them is a run the
|
|
1691
|
+
* user stopped — see the status derivation for why a stop usually leaves no
|
|
1692
|
+
* other trace in the messages. Ids rather than a file key on purpose: they name
|
|
1693
|
+
* one RUN, so a later re-index of the same file cannot inherit the stop. */
|
|
1694
|
+
stoppedIndexIds?: {
|
|
1695
|
+
[serverItemId: string]: boolean;
|
|
1696
|
+
};
|
|
1697
|
+
/** Whether the WORKER drives the windowed text/grid loop (chatEngineConfig's
|
|
1698
|
+
* windowedIndexing). Passed in rather than read from config so this stays a
|
|
1699
|
+
* pure function of its inputs and can be exercised for both settings. */
|
|
1700
|
+
windowedIndexing?: boolean;
|
|
1175
1701
|
};
|
|
1176
1702
|
declare function parseIndexingLabel(content: string): {
|
|
1177
1703
|
name: string;
|
|
@@ -1238,7 +1764,198 @@ declare class ChatSession {
|
|
|
1238
1764
|
private _pauseReasons;
|
|
1239
1765
|
private _resuming;
|
|
1240
1766
|
private _lidSeq;
|
|
1767
|
+
private _stageSeq;
|
|
1768
|
+
/** How many attachment-upload batches are running. uploadingAttachments is a
|
|
1769
|
+
* single flag but batches overlap (the composer stays live, so the user can
|
|
1770
|
+
* send a second one while the first uploads), and a nested finish must not
|
|
1771
|
+
* clear the flag out from under the batch still running. */
|
|
1772
|
+
private _uploadBatches;
|
|
1773
|
+
/** Indexing requests whose ack has not come back yet. Until it does the item
|
|
1774
|
+
* is not on the server's queue, so awaitIndexingDrained cannot see it — and
|
|
1775
|
+
* would read the gap between "pass N settled" and "pass N+1 accepted" as the
|
|
1776
|
+
* file being finished. */
|
|
1777
|
+
private _indexDispatchesInFlight;
|
|
1778
|
+
/** Live awaitIndexingDrained waiters, one callback each. A nudge only pulls that
|
|
1779
|
+
* waiter's NEXT look forward; it can never make one conclude anything, so a
|
|
1780
|
+
* wrong nudge costs one pair of requests and the look reports busy. Overlapping
|
|
1781
|
+
* waiters are normal — the composer stays live, so a second send can be
|
|
1782
|
+
* uploading while the first waits. */
|
|
1783
|
+
private _drainNudges;
|
|
1784
|
+
/** Stages whose upload/dispatch chain is still running in THIS page. Lives and
|
|
1785
|
+
* dies with those chains, so it is what tells a staged bubble restored from the
|
|
1786
|
+
* history cache whether anything is still working on it (see
|
|
1787
|
+
* settleDeadStagedMessages). Today the cache dies with the page too and every
|
|
1788
|
+
* restored stage is live; this stays correct if that ever changes. */
|
|
1789
|
+
private _liveStages;
|
|
1790
|
+
/** Files the SERVER currently has unresolved indexing work for, by the same key
|
|
1791
|
+
* a collapsed row uses (storage path, else filename), and whether we have asked
|
|
1792
|
+
* even once for this chat.
|
|
1793
|
+
*
|
|
1794
|
+
* This is the only thing that can tell a WORKER-driven run (a PDF's page loop, a
|
|
1795
|
+
* windowed read) that it is over. Those chains are advanced inside the worker off
|
|
1796
|
+
* the renderer's page count; the client sees passes appear and settle and can
|
|
1797
|
+
* never tell "between passes" from "finished" by looking at them. Asking the
|
|
1798
|
+
* queue is how it finds out. Until it has asked, `checked` is false and the view
|
|
1799
|
+
* says "still working", which is the honest reading of not knowing — and the one
|
|
1800
|
+
* that does not repeat the bug where a row claimed "Indexed" mid-run. */
|
|
1801
|
+
/** The chat the live-index snapshot (state.liveIndexKeys) was taken for, so a
|
|
1802
|
+
* project switch drops it. */
|
|
1803
|
+
private _liveIndexKey;
|
|
1804
|
+
/** When the snapshot was last published (wall clock ms), so a caller that needs
|
|
1805
|
+
* a CURRENT answer can tell whether to re-ask. 0 = never. */
|
|
1806
|
+
private _liveIndexAt;
|
|
1807
|
+
/** Files this client has an index dispatch in flight for, by scoped path ->
|
|
1808
|
+
* wall clock. See claimIndexRun. */
|
|
1809
|
+
private _indexClaims;
|
|
1241
1810
|
constructor(host: ChatHost);
|
|
1811
|
+
/** What the display layer needs to decide whether a run is finished. `keys` holds
|
|
1812
|
+
* every file the server still has indexing work for; `checked` is false until the
|
|
1813
|
+
* first answer for this chat, and false means "we do not know yet". */
|
|
1814
|
+
getLiveIndexState(): {
|
|
1815
|
+
keys: {
|
|
1816
|
+
[fileKey: string]: boolean;
|
|
1817
|
+
};
|
|
1818
|
+
checked: boolean;
|
|
1819
|
+
};
|
|
1820
|
+
/** Passes that were on a row when the user stopped it, so the display layer can
|
|
1821
|
+
* still tell that this run was stopped once the stop has left no other trace.
|
|
1822
|
+
* See cancelIndexingGroup, which fills it, and buildChatDisplayList, which is
|
|
1823
|
+
* the only reader. */
|
|
1824
|
+
getStoppedIndexIds(): {
|
|
1825
|
+
[serverItemId: string]: boolean;
|
|
1826
|
+
};
|
|
1827
|
+
/**
|
|
1828
|
+
* Is this file ALREADY being indexed by this client?
|
|
1829
|
+
*
|
|
1830
|
+
* One live run per file, and the reason is what a second one looks like: the
|
|
1831
|
+
* conversation grows a SECOND collapsed row for the same file (a run is opened
|
|
1832
|
+
* by every FIRST pass, so two of them are two rows), the same document is read
|
|
1833
|
+
* twice at full provider cost, and the two chains fight over the same records —
|
|
1834
|
+
* the delete-then-repost that starts run 2 wipes what run 1 has saved so far.
|
|
1835
|
+
*
|
|
1836
|
+
* Asked of this client's own live work, so it cannot be wrong in the dangerous
|
|
1837
|
+
* direction: a queued/running pass keeps its bgTaskQueue entry until its bubble
|
|
1838
|
+
* settles, and a settled run answers false, which is what a genuine later
|
|
1839
|
+
* re-index needs.
|
|
1840
|
+
*
|
|
1841
|
+
* The retry that made this necessary: a chip whose INDEX request failed is
|
|
1842
|
+
* handed back to the composer to be retried on the next send, and an index
|
|
1843
|
+
* request can fail from the client's side (a lost ack, an expired token on the
|
|
1844
|
+
* response) while the server has already queued the pass. The retry then indexes
|
|
1845
|
+
* a file that was never not being indexed.
|
|
1846
|
+
*/
|
|
1847
|
+
hasLiveIndexRun(storagePath?: string): boolean;
|
|
1848
|
+
/** Storage paths are project-relative, and one ChatSession serves every
|
|
1849
|
+
* project, so a claim has to be scoped the way a stop is (_indexKeyOf). */
|
|
1850
|
+
private _indexClaimKey;
|
|
1851
|
+
/**
|
|
1852
|
+
* Take this file's indexing slot, or report that someone already has it.
|
|
1853
|
+
*
|
|
1854
|
+
* The check-and-CLAIM is what makes it safe against a second caller arriving
|
|
1855
|
+
* mid-flight: the claim is written SYNCHRONOUSLY, before the first await, so a
|
|
1856
|
+
* concurrent caller sees it even though no request has completed and no queue
|
|
1857
|
+
* has admitted anything. Ask-then-dispatch could not do that — every source it
|
|
1858
|
+
* consults only learns about a dispatch after the ack.
|
|
1859
|
+
*
|
|
1860
|
+
* Returns true when the caller owns the slot and should dispatch. A caller that
|
|
1861
|
+
* then fails to dispatch MUST releaseIndexRun, or the file waits out the claim
|
|
1862
|
+
* (a few minutes) before it can be retried.
|
|
1863
|
+
*/
|
|
1864
|
+
claimIndexRun(storagePath?: string): Promise<boolean>;
|
|
1865
|
+
/** Give the slot back — the dispatch failed, or was abandoned. */
|
|
1866
|
+
releaseIndexRun(storagePath?: string): void;
|
|
1867
|
+
/**
|
|
1868
|
+
* The same question, asked of the SERVER when this page cannot answer it.
|
|
1869
|
+
*
|
|
1870
|
+
* hasLiveIndexRun only knows what this page did. That is not enough for the
|
|
1871
|
+
* case duplicates actually come from: the first run was started before a
|
|
1872
|
+
* reload, or in another tab, or its bubble has since been paged out of the
|
|
1873
|
+
* loaded window — and then the retry finds nothing locally and starts a second
|
|
1874
|
+
* run of a file that is still being indexed. The queue is the one place that
|
|
1875
|
+
* knows, and it is already asked for exactly this list.
|
|
1876
|
+
*
|
|
1877
|
+
* Only a POSITIVE answer is used. Absence proves nothing here (the query is
|
|
1878
|
+
* capped, and `liveIndexChecked` records that), so an unanswerable question
|
|
1879
|
+
* falls back to dispatching — the cost of a wrong "no" is the duplicate this
|
|
1880
|
+
* exists to prevent, and the cost of a wrong "yes" is a file that never gets
|
|
1881
|
+
* indexed at all. Only one of those is recoverable by the user.
|
|
1882
|
+
*/
|
|
1883
|
+
isIndexRunLive(storagePath?: string): Promise<boolean>;
|
|
1884
|
+
/** Re-ask the queue which files are still being indexed, unless the answer we
|
|
1885
|
+
* have is younger than `maxAgeMs`. Shared by every caller that needs a current
|
|
1886
|
+
* one; the display layer's own refresh path is the adopt ladder. */
|
|
1887
|
+
private _refreshLiveIndexKeys;
|
|
1888
|
+
/**
|
|
1889
|
+
* Replace the live-index snapshot from a queue query's raw items.
|
|
1890
|
+
*
|
|
1891
|
+
* Whole-snapshot, never incremental: the query returns everything unresolved on
|
|
1892
|
+
* the queue, so a file MISSING from it is precisely the fact we are after. Merging
|
|
1893
|
+
* would make a finished file impossible to observe.
|
|
1894
|
+
*/
|
|
1895
|
+
private _recordLiveIndexKeys;
|
|
1896
|
+
/** Forget the snapshot: it describes ONE chat's queue, and the answer for the
|
|
1897
|
+
* project the user just switched to is unknown until it is asked for again. */
|
|
1898
|
+
private _resetLiveIndexKeys;
|
|
1899
|
+
/**
|
|
1900
|
+
* Ask the queue what is still indexing, once, for the chat that is on screen.
|
|
1901
|
+
*
|
|
1902
|
+
* Seeds the snapshot on a history load. Without it a reloaded chat has no way to
|
|
1903
|
+
* learn that a run it can see is over: the adopt ladder that normally answers this
|
|
1904
|
+
* only fires when a pass SETTLES, and after a reload there is no pass left to
|
|
1905
|
+
* settle — so every finished worker-driven row would spin forever.
|
|
1906
|
+
*
|
|
1907
|
+
* Best-effort: a failure leaves `checked` false, which reads as "still working"
|
|
1908
|
+
* rather than as a false all-clear.
|
|
1909
|
+
*
|
|
1910
|
+
* Delegates to the adopt ladder rather than asking once. A single empty look is
|
|
1911
|
+
* exactly what that ladder exists to distrust — the worker writes pass N+1 a few
|
|
1912
|
+
* milliseconds AFTER flipping pass N to resolved, so a query landing in that gap
|
|
1913
|
+
* sees an empty queue for a chain that is very much alive. One look would turn
|
|
1914
|
+
* that into a confident "Indexed" with a green check, on the one scenario this
|
|
1915
|
+
* whole feature is for, and nothing would ever re-ask: the ladder is normally
|
|
1916
|
+
* triggered by a pass SETTLING, and after a reload there is no pass left to
|
|
1917
|
+
* settle. The ladder re-asks at 0/2s/6s, records each answer, and as a bonus
|
|
1918
|
+
* adopts and polls any live pass it finds, which makes the row genuinely active
|
|
1919
|
+
* instead of merely unconfirmed.
|
|
1920
|
+
*/
|
|
1921
|
+
refreshLiveIndexState(): void;
|
|
1922
|
+
/** Forget what we know about which files are indexing — but ONLY when the
|
|
1923
|
+
* snapshot was taken for a different chat than the one on screen now. For a
|
|
1924
|
+
* consumer whose history loading is its own fork and so never reaches
|
|
1925
|
+
* loadHistory's reset — a snapshot describes ONE chat's queue, and carrying it
|
|
1926
|
+
* into another project would let a row there claim to be finished on someone
|
|
1927
|
+
* else's evidence.
|
|
1928
|
+
*
|
|
1929
|
+
* Conditional for the same reason loadHistory's own reset is (the
|
|
1930
|
+
* `loadKey !== _liveIndexKey` gate): the view calls this on every mount, and
|
|
1931
|
+
* an unconditional wipe turned every re-entry to the chat into a grey
|
|
1932
|
+
* "Checking status:" sweep across rows whose state was already known. A
|
|
1933
|
+
* RE-entry keeps showing the last answer (green/yellow) while the first-page
|
|
1934
|
+
* refresh re-asks quietly; only a genuine project/platform switch starts from
|
|
1935
|
+
* "not known yet". Claiming `_liveIndexKey` here (before any answer) is the
|
|
1936
|
+
* same fudge loadHistory makes: it marks WHOSE chat the empty snapshot is
|
|
1937
|
+
* for, so repeated calls do not re-wipe, and _recordLiveIndexKeys re-claims
|
|
1938
|
+
* it when the real answer lands. */
|
|
1939
|
+
resetLiveIndexState(): void;
|
|
1940
|
+
/** Wrap an indexing-request dispatch so awaitIndexingDrained counts it as
|
|
1941
|
+
* live work from the moment it is sent, not from the moment it is acked. */
|
|
1942
|
+
trackIndexDispatch<T>(p: Promise<T>): Promise<T>;
|
|
1943
|
+
/**
|
|
1944
|
+
* Something just happened that plausibly ENDED indexing work, so let any waiting
|
|
1945
|
+
* turn look now instead of sitting out the rest of its busy interval.
|
|
1946
|
+
*
|
|
1947
|
+
* A nudge changes only WHEN a look happens, never what it concludes: the two
|
|
1948
|
+
* agreeing idle looks, the confirm gap between them, "a failed look counts as
|
|
1949
|
+
* busy" and the minimum wait are all untouched. That is why it is safe to fire
|
|
1950
|
+
* from places that are merely good guesses.
|
|
1951
|
+
*
|
|
1952
|
+
* Fired from end-of-chain points ONLY: the adopt ladder giving up, a resume
|
|
1953
|
+
* declining to continue, a pass failing. Not from every settling pass (one nudge
|
|
1954
|
+
* per pass per file for the whole run), and not from an indexing request being
|
|
1955
|
+
* accepted — see the note in trackIndexDispatch for why that one is actively
|
|
1956
|
+
* harmful rather than merely wasteful.
|
|
1957
|
+
*/
|
|
1958
|
+
private _nudgeIndexingDrain;
|
|
1242
1959
|
/**
|
|
1243
1960
|
* Register a live poll so (a) a remount dedupes against it instead of stacking a
|
|
1244
1961
|
* SECOND poll on the same item, and (b) pausePolling can stop it.
|
|
@@ -1247,6 +1964,12 @@ declare class ChatSession {
|
|
|
1247
1964
|
* poll simply cannot be stopped and is left running — see pausePolling.
|
|
1248
1965
|
*/
|
|
1249
1966
|
private _trackPoll;
|
|
1967
|
+
/** Background polls currently attached, for the MAX_CONCURRENT_BG_POLLS budget.
|
|
1968
|
+
* Counts the registry rather than a separate tally so it cannot drift: every
|
|
1969
|
+
* attach goes through _trackPoll and every detach deletes the entry. Note an
|
|
1970
|
+
* entry left behind by pausePolling on an older skapi-js (no stop handle)
|
|
1971
|
+
* still counts, which is correct — that poll really is still running. */
|
|
1972
|
+
private _countBgPolls;
|
|
1250
1973
|
/**
|
|
1251
1974
|
* Stop and forget one item's poll. Used after a cancel: the row is either gone
|
|
1252
1975
|
* (cancelled while queued) or flagged cancelled (cancelled while running), so
|
|
@@ -1292,7 +2015,7 @@ declare class ChatSession {
|
|
|
1292
2015
|
*/
|
|
1293
2016
|
private _applyReplyToCache;
|
|
1294
2017
|
/**
|
|
1295
|
-
*
|
|
2018
|
+
* projectId/owner are passed explicitly by every caller: a request can be
|
|
1296
2019
|
* dispatched after the user moved to another project, and re-reading the live
|
|
1297
2020
|
* identity here would silently send the turn to THAT project instead of the
|
|
1298
2021
|
* one it was composed for. Falls back to the live read only when a caller
|
|
@@ -1300,9 +2023,106 @@ declare class ChatSession {
|
|
|
1300
2023
|
*/
|
|
1301
2024
|
private _callProviderFor;
|
|
1302
2025
|
dispatchAgentRequest(params: any): Promise<any>;
|
|
2026
|
+
/**
|
|
2027
|
+
* Put a turn on screen the INSTANT the user hits Send, before its attachments
|
|
2028
|
+
* have finished uploading. Uploads run in the background now (the composer is
|
|
2029
|
+
* cleared and stays usable), so without a staged bubble the message would
|
|
2030
|
+
* appear only once its files were up — below anything the user sent in the
|
|
2031
|
+
* meantime, in an order that never matches what they typed.
|
|
2032
|
+
*
|
|
2033
|
+
* Staged bubbles carry _useBgQueue because that is where a turn with
|
|
2034
|
+
* attachments ultimately dispatches (behind its own indexing tasks). That flag
|
|
2035
|
+
* is also what keeps promoteNextQueuedToRunning / resolveQueuedUserBubble off
|
|
2036
|
+
* them: those advance the SERVER queue, and a staged turn has no server
|
|
2037
|
+
* request behind it yet.
|
|
2038
|
+
*
|
|
2039
|
+
* Returns the id to hand back as PinnedDispatchContext.stageId at dispatch.
|
|
2040
|
+
*/
|
|
2041
|
+
stageOutgoingMessage(displayText: string): string;
|
|
2042
|
+
/** Is anything in this page still uploading/dispatching for this stage? */
|
|
2043
|
+
isLiveStage(stageId?: string): boolean;
|
|
2044
|
+
/**
|
|
2045
|
+
* Settle any staged bubble in `list` whose chain no longer exists, and return the
|
|
2046
|
+
* list (a new array only if something changed).
|
|
2047
|
+
*
|
|
2048
|
+
* The caller is a cache restore. A staged bubble is the one kind of message whose
|
|
2049
|
+
* resolution lives entirely in page memory — no server request stands behind it
|
|
2050
|
+
* yet — so a copy that outlives its upload would render "(Uploading files...)"
|
|
2051
|
+
* forever with nothing left to finish it. Today nothing can: this cache dies with
|
|
2052
|
+
* the page, so every restored stage is still live and this is a no-op. It exists
|
|
2053
|
+
* so that stops being a silent assumption.
|
|
2054
|
+
*/
|
|
2055
|
+
settleDeadStagedMessages(list: ChatMessage[]): ChatMessage[];
|
|
2056
|
+
private _stageIndex;
|
|
2057
|
+
/**
|
|
2058
|
+
* Staged turn, phase 2: its files are up and it is now waiting for the whole
|
|
2059
|
+
* indexing chain behind them. Swaps "(Uploading files...)" for
|
|
2060
|
+
* "(Indexing files...)"; the bubble stays dimmed, because from the user's side
|
|
2061
|
+
* nothing has been handed over yet.
|
|
2062
|
+
*
|
|
2063
|
+
* It deliberately does NOT say "(In queue)" here. The turn is not queued behind
|
|
2064
|
+
* anything the server knows about yet — it is waiting on work that can run for
|
|
2065
|
+
* minutes — and claiming otherwise is what made the wait look like a stall.
|
|
2066
|
+
*/
|
|
2067
|
+
markStagedMessageIndexing(stageId: string): void;
|
|
2068
|
+
/**
|
|
2069
|
+
* Staged turn, phase 3: the last of its files has finished indexing, so the turn
|
|
2070
|
+
* is genuinely just queued now. Full opacity + "(In queue)".
|
|
2071
|
+
*
|
|
2072
|
+
* Clears the PRESENTATIONAL _dimSending only; isSendingToServer stays set until
|
|
2073
|
+
* the server actually acks (it is the token that ack matches on). Called by the
|
|
2074
|
+
* clients the instant awaitIndexingDrained resolves, i.e. immediately before the
|
|
2075
|
+
* dispatch that replaces this bubble — dispatchComposedMessage carries the
|
|
2076
|
+
* cleared flag onto the replacement so the turn does not blink back to dimmed.
|
|
2077
|
+
*/
|
|
2078
|
+
markStagedMessageReady(stageId: string): void;
|
|
2079
|
+
/**
|
|
2080
|
+
* Resolves once this project's background-indexing queue has nothing left to
|
|
2081
|
+
* run, so a chat enqueued right after it is genuinely last.
|
|
2082
|
+
*
|
|
2083
|
+
* Sending the chat as soon as the uploads finish is not enough, which is the
|
|
2084
|
+
* whole reason this exists: indexing a file is a CHAIN, and each pass is only
|
|
2085
|
+
* enqueued once the previous one lands (the client mints CONTINUE passes for
|
|
2086
|
+
* text/grid files, the worker mints them for PDFs and windowed reads). Every
|
|
2087
|
+
* one of those passes therefore queues up BEHIND a chat sent at upload time,
|
|
2088
|
+
* and the model answers from a file it has only partly read.
|
|
2089
|
+
*
|
|
2090
|
+
* The queue is read from the server's status index rather than from
|
|
2091
|
+
* bgTaskQueue: that mirror holds only what this client dispatched or adopted,
|
|
2092
|
+
* and it stops being maintained once the view unmounts. An empty answer has to
|
|
2093
|
+
* repeat before it is believed — see INDEXING_DRAIN_IDLE_LOOKS — and a look
|
|
2094
|
+
* that fails counts as busy, so a dropped request delays the turn instead of
|
|
2095
|
+
* releasing it early.
|
|
2096
|
+
*
|
|
2097
|
+
* Reads the identity PINNED at Send time, never a live one: the user may be in
|
|
2098
|
+
* another project by now, and this must keep asking about the one they sent
|
|
2099
|
+
* from.
|
|
2100
|
+
*/
|
|
2101
|
+
awaitIndexingDrained(identity: ChatIdentity): Promise<'drained' | 'timedout' | 'skipped'>;
|
|
2102
|
+
/**
|
|
2103
|
+
* Abandon a staged turn — its uploads failed outright, so nothing will be
|
|
2104
|
+
* dispatched. The bubble stays (the user's text is not silently thrown away)
|
|
2105
|
+
* but settles into a plain, non-pending message; the caller reports the
|
|
2106
|
+
* failure separately.
|
|
2107
|
+
*/
|
|
2108
|
+
settleStagedMessage(stageId: string): void;
|
|
1303
2109
|
dispatchComposedMessage(composed: string, useBgQueue?: boolean, composedForLlm?: string, extractContent?: any, fileUrls?: any, pinned?: PinnedDispatchContext): void;
|
|
1304
2110
|
promoteNextBgQueuedToRunning(): void;
|
|
1305
2111
|
promoteNextQueuedToRunning(): void;
|
|
2112
|
+
/**
|
|
2113
|
+
* The "Thinking..." placeholder belonging to the user bubble at `userIdx`, or -1.
|
|
2114
|
+
*
|
|
2115
|
+
* Every path that creates one puts it IMMEDIATELY after its user bubble
|
|
2116
|
+
* (promoteNextQueuedToRunning, the immediate-send pair, applyHistoryItemResolution),
|
|
2117
|
+
* so ownership is adjacency — modulo background bubbles, which get spliced in
|
|
2118
|
+
* around them. Taking the first pending assistant ANYWHERE below instead was a
|
|
2119
|
+
* hijack: a turn sent with attachments never gets a placeholder of its own
|
|
2120
|
+
* (promoteNextQueuedToRunning skips _useBgQueue turns) and now keeps the position
|
|
2121
|
+
* it was sent in, so an ordinary turn sent while its files indexed sits BELOW it
|
|
2122
|
+
* with a placeholder of its own — and the attachment turn's answer was rendered
|
|
2123
|
+
* as the answer to that unrelated question.
|
|
2124
|
+
*/
|
|
2125
|
+
private _ownThinkingIndex;
|
|
1306
2126
|
resolveQueuedUserBubble(serverId?: string): number | undefined;
|
|
1307
2127
|
insertAtTarget(msg: ChatMessage, targetIdx: number): void;
|
|
1308
2128
|
onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string, ownerKey?: string): void;
|
|
@@ -1321,7 +2141,10 @@ declare class ChatSession {
|
|
|
1321
2141
|
* 2. the file is remembered in cancelledIndexKeys, so the client-driven
|
|
1322
2142
|
* resume (maybeResumeIndexing) stops dispatching CONTINUE passes; and
|
|
1323
2143
|
* 3. any of its passes still sitting in bgTaskQueue is dropped by the next
|
|
1324
|
-
* drain rather than surfacing a fresh "Indexing…" bubble
|
|
2144
|
+
* drain rather than surfacing a fresh "Indexing…" bubble; and
|
|
2145
|
+
* 4. the RUN is remembered (state.stoppedIndexIds), because none of the above
|
|
2146
|
+
* necessarily leaves a mark on the conversation — see below — and without
|
|
2147
|
+
* it the collapsed row reported the stopped file as finished.
|
|
1325
2148
|
*
|
|
1326
2149
|
* Records already written by the passes that DID run are kept — this stops the
|
|
1327
2150
|
* work, it does not undo it.
|
|
@@ -1332,12 +2155,48 @@ declare class ChatSession {
|
|
|
1332
2155
|
enqueueTypewrite(idx: number, fullText: string, localId?: string): Promise<any>;
|
|
1333
2156
|
typewriteLatestReply(key: string): Promise<any>;
|
|
1334
2157
|
_removeStrayPendingAssistants(): void;
|
|
2158
|
+
/** Index of the USER bubble the message at `idx` belongs to — the nearest one
|
|
2159
|
+
* above it, stepping over background bubbles (a file's indexing rows are
|
|
2160
|
+
* inserted between turns). -1 when the nearest thing above is not a user turn,
|
|
2161
|
+
* which for a placeholder means it is an orphan. */
|
|
2162
|
+
private _owningUserIndex;
|
|
2163
|
+
/** The bubble at `idx` is the "Thinking…" of a DIFFERENT turn that is still
|
|
2164
|
+
* waiting for its answer, so the sweep above must leave it alone. */
|
|
2165
|
+
private _isLiveImmediatePlaceholder;
|
|
2166
|
+
/** A pending assistant at `idx` is the placeholder OF the turn above it, so a
|
|
2167
|
+
* reply may take its slot. Every path that makes one copies the parent's
|
|
2168
|
+
* _serverItemId (or neither has one yet), so a mismatch means the slot belongs to
|
|
2169
|
+
* some other request and the reply must be spliced in beside it, not on top. */
|
|
2170
|
+
private _isOwnPlaceholderOf;
|
|
1335
2171
|
_clearPendingUserBubble(itemId: string): void;
|
|
1336
2172
|
resumePendingRequest(token: number): Promise<void>;
|
|
1337
2173
|
handleHistoryItemResolution(itemId: string, response: any, platform: string): void;
|
|
1338
2174
|
/** The file an already-rendered background pass is about, off its request
|
|
1339
2175
|
* bubble. Null for an ordinary turn, which is most of them. */
|
|
1340
2176
|
private _indexRefOfItem;
|
|
2177
|
+
/**
|
|
2178
|
+
* Settle a turn the server reports as cancelled: the request bubble goes to its
|
|
2179
|
+
* cancelled form and the "Thinking..." placeholder goes away. The same shape
|
|
2180
|
+
* cancelQueuedMessage produces locally, so a cancel this client made and one it
|
|
2181
|
+
* merely found out about render identically — and an indexing pass keeps the
|
|
2182
|
+
* markers that hold it in its file's collapsed row.
|
|
2183
|
+
*/
|
|
2184
|
+
private _settleCancelledItem;
|
|
2185
|
+
/**
|
|
2186
|
+
* A poll that came back saying the request was CANCELLED, rather than with an
|
|
2187
|
+
* answer.
|
|
2188
|
+
*
|
|
2189
|
+
* The server keeps a cancelled request as a terminal row instead of deleting it
|
|
2190
|
+
* (that row is the durable record of the stop, and the chat history it belongs
|
|
2191
|
+
* to), so a poll still running when the cancel lands now RESOLVES on it. It used
|
|
2192
|
+
* to reject with NOT_EXISTS, and the resolution path below reads a status object
|
|
2193
|
+
* as an answer with no text — which would stamp "No text response received from
|
|
2194
|
+
* AI provider" over a turn the user had just stopped.
|
|
2195
|
+
*
|
|
2196
|
+
* Reachable whenever the poll was not stopped by whoever cancelled: another tab,
|
|
2197
|
+
* another device, or the row being cancelled server-side by the file's own stop.
|
|
2198
|
+
*/
|
|
2199
|
+
private _isCancelledPollResult;
|
|
1341
2200
|
applyHistoryItemResolution(itemId: string, response: any, platform: string): void;
|
|
1342
2201
|
/** How a bg task maps onto a collapsed row: the row's own key (storage path
|
|
1343
2202
|
* when known, else the filename), scoped to the chat it belongs to. A storage
|
|
@@ -1353,6 +2212,16 @@ declare class ChatSession {
|
|
|
1353
2212
|
* path, and without this an earlier cancel would silently kill every future
|
|
1354
2213
|
* index of the same path. A continuation of a stopped file is dropped instead,
|
|
1355
2214
|
* covering the pass that was dispatched in the moment before the cancel landed.
|
|
2215
|
+
*
|
|
2216
|
+
* "Fresh" is the load-bearing word, and it used to be missing. A run's OWN first
|
|
2217
|
+
* pass sits in this queue for as long as it runs (entries are only dropped once
|
|
2218
|
+
* their bubble settles), so stopping a file during its first pass — which is
|
|
2219
|
+
* exactly when a user who has just uploaded it does — met that first-pass entry
|
|
2220
|
+
* on the very next drain and lifted the stop the user had just asked for. The
|
|
2221
|
+
* chain then carried on, one worker-minted window after another, with nothing
|
|
2222
|
+
* client-side left to suppress it. The ids recorded at stop time are what tells
|
|
2223
|
+
* the two apart: a pass that was already there when the user hit Stop cannot be
|
|
2224
|
+
* the new request that lifts it.
|
|
1356
2225
|
*/
|
|
1357
2226
|
private _applyIndexCancellations;
|
|
1358
2227
|
/**
|
|
@@ -1418,12 +2287,12 @@ declare class ChatSession {
|
|
|
1418
2287
|
drainBgTaskQueue(): void;
|
|
1419
2288
|
maybeResumeIndexing(entry: BgTaskEntry, response: any, platform: string): void;
|
|
1420
2289
|
loadHistory(fetchMore?: boolean, token?: number): Promise<void>;
|
|
1421
|
-
uploadSingleAttachment(att: any): Promise<Array<{
|
|
2290
|
+
uploadSingleAttachment(att: any, stageId?: string): Promise<Array<{
|
|
1422
2291
|
name: string;
|
|
1423
2292
|
url: string;
|
|
1424
2293
|
storagePath: string;
|
|
1425
2294
|
}>>;
|
|
1426
|
-
uploadPendingAttachments(): Promise<Array<{
|
|
2295
|
+
uploadPendingAttachments(batchId?: string, stageId?: string): Promise<Array<{
|
|
1427
2296
|
name: string;
|
|
1428
2297
|
url: string;
|
|
1429
2298
|
storagePath?: string;
|
|
@@ -1432,4 +2301,4 @@ declare class ChatSession {
|
|
|
1432
2301
|
bumpGate(): void;
|
|
1433
2302
|
}
|
|
1434
2303
|
|
|
1435
|
-
export { type AiAgentPlatform, type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, BOM, BOM_EXTS, 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, EXT_CONTENT_TYPES, type EncodingClass, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_BUDGET_RATIO, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, HTML_EXTS, HTML_HEAD_WINDOW, 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, RTF_EXTS, TOOL_AND_RESPONSE_BUFFER, XML_EXTS, applyEncodingDeclaration, buildAiAgentValue, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, contentTypeForExt, createHistoryFiller, createInlineLinkRegex, encodePathSegments, encodingClassForExt, ensureHtmlCharset, ensureXmlEncoding, escapeRtfNonAscii, estimateMessageTokens, estimateTextTokens, extOf, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, getProjectContextWindow, groupAttachmentFailures, hasBom, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isIndexingRequestText, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, looksLikeRtf, makeExtractPlaceholder, mapHistoryListToMessages, needsBomForExt, normalizeAttachmentPathCandidate, normalizeExt, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAiAgentValue, parseAttachmentContent, parseIndexingLabel, parseIndexingRequestText, prepareDownloadText, readExpiredAttachmentHref, registerAttachmentParser, registerModelContextWindows, repairUrlEntities, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, setProjectContextWindow, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, wallClockNow };
|
|
2304
|
+
export { type AiAgentPlatform, type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, BOM, BOM_EXTS, 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, EMPTY_INDEXING_REPLY, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, EXT_CONTENT_TYPES, type EncodingClass, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_BUDGET_RATIO, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, HTML_EXTS, HTML_HEAD_WINDOW, IMAGE_PREVIEWS_PER_MESSAGE, INDEXING_COMPLETE_MARKER, INLINE_LINK_GLYPH, INLINE_LINK_UNAVAILABLE_GLYPH, INLINE_LINK_UNAVAILABLE_SUFFIX, type ImagePreviewContext, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingRequestRef, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkMarkupOptions, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_CONCURRENT_BG_POLLS, 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, PREVIEWABLE_IMAGE_CONTENT_TYPES, PREVIEW_BROWSER_CACHE_SECONDS, type ParsedAiAgent, type PinnedDispatchContext, type PreviewImageEl, RENDER_FROM_TOKEN, RTF_EXTS, type RenderableInlineLink, TOOL_AND_RESPONSE_BUFFER, type VisionProfile, XML_EXTS, applyEncodingDeclaration, bgIndexingQueueName, buildAiAgentValue, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, clearImagePreviewCache, composeUserMessage, configureChatEngine, contentTypeForExt, createHistoryFiller, createInlineLinkRegex, encodePathSegments, encodingClassForExt, ensureHtmlCharset, ensureXmlEncoding, escapeInlineHtml, escapeRtfNonAscii, estimateMessageTokens, estimateTextTokens, extOf, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, getProjectContextWindow, getVisionProfile, groupAttachmentFailures, hasBom, hydrateImagePreviews, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isIndexingRequestText, isLinkUnavailable, isNonRetryableRequestError, isOfficeFile, isPreviewableImagePath, isServerExtractable, isServiceDbAttachmentHref, linkUnavailableKeyForHref, linkUnavailableKeyForPath, listClaudeModels, listOpenAIModels, looksLikeRtf, makeExtractPlaceholder, mapHistoryListToMessages, markImagePreviewStale, needsBomForExt, normalizeAttachmentPathCandidate, normalizeExt, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAiAgentValue, parseAttachmentContent, parseIndexingLabel, parseIndexingRequestText, peekImagePreviewUrl, prepareDownloadText, previewImageContentType, previewableExtOf, readExpiredAttachmentHref, registerAttachmentParser, registerModelContextWindows, renderInlineLinkHtml, repairUrlEntities, repairUrlWhitespace, resolveImagePreviewUrl, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, setProjectContextWindow, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, wallClockNow };
|