bunnyquery 1.6.0 → 1.7.0
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 +157 -29
- package/bunnyquery.css +122 -0
- package/bunnyquery.js +1400 -195
- package/dist/engine.cjs +1119 -117
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +517 -12
- package/dist/engine.d.ts +517 -12
- package/dist/engine.mjs +1103 -118
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/config.ts +19 -0
- package/src/engine/history.ts +20 -2
- package/src/engine/host.ts +47 -1
- package/src/engine/index.ts +25 -1
- package/src/engine/indexing_groups.ts +350 -0
- package/src/engine/links.ts +309 -9
- package/src/engine/office.ts +53 -5
- package/src/engine/prompts/chat_system_prompt.ts +1 -1
- package/src/engine/prompts/index.ts +3 -0
- package/src/engine/prompts/indexing_user_message.ts +110 -29
- package/src/engine/requests.ts +107 -20
- package/src/engine/session.ts +741 -82
- package/src/engine/viewport_fill.ts +186 -0
- package/styles/chat.css +122 -0
package/dist/engine.d.mts
CHANGED
|
@@ -81,6 +81,20 @@ interface ChatEngineConfig {
|
|
|
81
81
|
* `registerAttachmentParser()`. See attachment_parsers.ts.
|
|
82
82
|
*/
|
|
83
83
|
attachmentParsers?: AttachmentParser[];
|
|
84
|
+
/**
|
|
85
|
+
* Opt in to SERVER-DRIVEN windowed indexing for text/grid files.
|
|
86
|
+
*
|
|
87
|
+
* Off by default, and deliberately so. When on, the client emits a
|
|
88
|
+
* `_skapi_window` directive and the WORKER reads the file one window at a time,
|
|
89
|
+
* continuing until the reader says it is exhausted. When off, the agent pages the
|
|
90
|
+
* file itself with readFileContent, exactly as before.
|
|
91
|
+
*
|
|
92
|
+
* The flag exists because the backend must ship FIRST: a client emitting the
|
|
93
|
+
* directive against a worker that does not strip it leaves an unknown field in the
|
|
94
|
+
* request body, and the provider rejects the whole call with no retry. Keep it off
|
|
95
|
+
* until the worker is deployed, then flip it per environment.
|
|
96
|
+
*/
|
|
97
|
+
windowedIndexing?: boolean;
|
|
84
98
|
}
|
|
85
99
|
declare function configureChatEngine(config: ChatEngineConfig): void;
|
|
86
100
|
declare function chatEngineConfig(): ChatEngineConfig;
|
|
@@ -241,18 +255,46 @@ type BuildIndexingUserMessageOptions = {
|
|
|
241
255
|
pagedRead?: boolean;
|
|
242
256
|
};
|
|
243
257
|
declare function buildIndexingUserMessage(attachment: IndexingAttachmentInfo, options?: BuildIndexingUserMessageOptions): string;
|
|
258
|
+
/**
|
|
259
|
+
* Token the WORKER substitutes with the 1-based first page of the window it is about to
|
|
260
|
+
* render, when it builds the next pass of a document from `RENDER_CONTINUE_TEMPLATE`.
|
|
261
|
+
* Must match the worker's RENDER_FROM_TOKEN.
|
|
262
|
+
*/
|
|
263
|
+
declare const RENDER_FROM_TOKEN = "{{RENDER_FROM}}";
|
|
244
264
|
/**
|
|
245
265
|
* User message for a VISION file (PDF): its pages are delivered as RENDERED PAGE IMAGES that
|
|
246
266
|
* the proxy worker injects into THIS message at the `placeholder` token (tool-result images
|
|
247
267
|
* render on neither provider, so the pages must be image blocks in the message itself). Each
|
|
248
|
-
* pass shows one WINDOW of pages starting at `renderFrom` (0-based)
|
|
249
|
-
*
|
|
268
|
+
* pass shows one WINDOW of pages starting at `renderFrom` (0-based).
|
|
269
|
+
*
|
|
270
|
+
* The WORKER advances the window: when its renderer reports pages remaining it enqueues the
|
|
271
|
+
* next pass itself, off the true page count, so a document indexes end-to-end with no browser
|
|
272
|
+
* involved. This message therefore only ever describes ONE window, and the model is never
|
|
273
|
+
* asked to decide whether the document is finished.
|
|
250
274
|
*
|
|
251
275
|
* renderFrom === 0 is the FIRST pass (leads with "A new file has just been uploaded." so the
|
|
252
|
-
* client builds the "Indexing: <name>" bubble);
|
|
253
|
-
* "CONTINUE indexing"
|
|
276
|
+
* client builds the "Indexing: <name>" bubble); a continue pass (built by the worker from
|
|
277
|
+
* buildIndexingRenderContinueTemplate) leads with "CONTINUE indexing" so it is not a duplicate
|
|
278
|
+
* primary bubble.
|
|
254
279
|
*/
|
|
255
280
|
declare function buildIndexingRenderMessage(attachment: IndexingAttachmentInfo, placeholder: string, renderFrom: number): string;
|
|
281
|
+
/**
|
|
282
|
+
* The CONTINUE pass, as a template the worker fills in. `pageLabel` defaults to the
|
|
283
|
+
* RENDER_FROM_TOKEN placeholder, which the worker replaces with the real 1-based start page
|
|
284
|
+
* of the window it is rendering; passing an explicit label produces a ready-to-send message.
|
|
285
|
+
*/
|
|
286
|
+
declare function buildIndexingRenderContinueTemplate(attachment: IndexingAttachmentInfo, placeholder: string, pageLabel?: string): string;
|
|
287
|
+
/**
|
|
288
|
+
* User message for a WINDOWED file: the worker splices ONE window of the file's rows or
|
|
289
|
+
* text into this message at `placeholder`, then continues from the reader's own cursor
|
|
290
|
+
* until the file is exhausted.
|
|
291
|
+
*
|
|
292
|
+
* The agent is deliberately NOT asked to page the file itself, and is NOT asked to judge
|
|
293
|
+
* whether it is finished. Both used to be its job, and both failed the same way: the
|
|
294
|
+
* traversal lived inside a single turn's budget, so a large file simply stopped partway
|
|
295
|
+
* with a confident summary of the part it had seen.
|
|
296
|
+
*/
|
|
297
|
+
declare function buildIndexingWindowMessage(attachment: IndexingAttachmentInfo, placeholder: string, isContinuation: boolean, positionLabel?: string): string;
|
|
256
298
|
/**
|
|
257
299
|
* User message for a RESUME pass: a previous indexing pass could not finish this large
|
|
258
300
|
* file, so continue it from where the already-saved records leave off (never restart).
|
|
@@ -314,6 +356,26 @@ declare function buildBoundedChatMessages(options: BoundedChatOptions): {
|
|
|
314
356
|
declare var EXPIRED_ATTACHMENT_URL_HOST: string;
|
|
315
357
|
declare var EXPIRED_ATTACHMENT_URL_ORIGIN: string;
|
|
316
358
|
declare var LINK_LABEL_MAX_DISPLAY_CHARS: number;
|
|
359
|
+
/**
|
|
360
|
+
* Lifetime of the url minted when a user clicks an expired attachment chip.
|
|
361
|
+
*
|
|
362
|
+
* Mint it as a PLAIN get-db presign, never with generate_temporary_cdn_url: the
|
|
363
|
+
* cdn branch ignores `expires` entirely and hands back a url good for the rest of
|
|
364
|
+
* the current UTC day plus the next one, so a "20 minute" link would in fact live
|
|
365
|
+
* 24 to 48 hours. The dashboard has always done this correctly and the widget did
|
|
366
|
+
* not, which is precisely the kind of divergence a shared constant exists to stop.
|
|
367
|
+
*/
|
|
368
|
+
declare var EXPIRED_LINK_REFRESH_EXPIRES_SECONDS: number;
|
|
369
|
+
/**
|
|
370
|
+
* How long a client may keep serving an href it already minted before dropping
|
|
371
|
+
* back to the placeholder and re-minting.
|
|
372
|
+
*
|
|
373
|
+
* DERIVED from the TTL above, with five minutes of headroom, because the
|
|
374
|
+
* invariant "the cache must expire before the url does" used to be a comment
|
|
375
|
+
* next to two independent literals. If it is ever violated a client serves a
|
|
376
|
+
* dead url with no way to notice; deriving it makes that unrepresentable.
|
|
377
|
+
*/
|
|
378
|
+
declare var LINK_REFRESH_WINDOW_MS: number;
|
|
317
379
|
declare function createInlineLinkRegex(): RegExp;
|
|
318
380
|
declare function safeDecodeURIComponent(v: string): string;
|
|
319
381
|
declare function encodePathSegments(path: string): string;
|
|
@@ -322,7 +384,86 @@ declare function extractRemotePathFromAttachmentHref(href: string, serviceId: st
|
|
|
322
384
|
declare function getExpiredAttachmentVisiblePath(remotePath: string, fallback?: string): string;
|
|
323
385
|
declare function buildDisplayExpiredAttachmentHref(remotePath: string, fallback?: string): string;
|
|
324
386
|
declare function isServiceDbAttachmentHref(href: string, serviceId: string): boolean;
|
|
387
|
+
/**
|
|
388
|
+
* Read the storage path back out of an `_expired_.url` placeholder.
|
|
389
|
+
*
|
|
390
|
+
* The placeholder is not a display detail: sanitizeAttachmentLinksForHistory
|
|
391
|
+
* writes it into PERSISTED history, and buildBoundedChatMessages replays it into
|
|
392
|
+
* the model's context. So it round-trips constantly and MUST be recognised on the
|
|
393
|
+
* way back in. Returns null for anything that is not the carrier.
|
|
394
|
+
*/
|
|
395
|
+
declare function readExpiredAttachmentHref(href: string): string | null;
|
|
325
396
|
declare function sanitizeAttachmentLinksForHistory(content: string, serviceId: string, forAssistant?: boolean): string;
|
|
397
|
+
/**
|
|
398
|
+
* Is this markdown link target a URL rather than a db storage path?
|
|
399
|
+
*
|
|
400
|
+
* The inline-link regex decides that by whether the target contains whitespace:
|
|
401
|
+
* its url branch forbids it, its bare-path branch allows it (a db path really can
|
|
402
|
+
* contain spaces). So a url that picked up a stray space anywhere in transit
|
|
403
|
+
* falls out of the url branch and is claimed by the path branch, and the view
|
|
404
|
+
* renders it as an `_expired_.url/https%3A/…` attachment chip that resolves to
|
|
405
|
+
* nothing. The view asks this FIRST, so what a link IS never depends on damage.
|
|
406
|
+
*/
|
|
407
|
+
declare function isHttpUrlLike(target: string): boolean;
|
|
408
|
+
/**
|
|
409
|
+
* Repair whitespace inside a url. RFC 3986 has no legal whitespace anywhere in a
|
|
410
|
+
* URI, so a space in an href is always damage, never content.
|
|
411
|
+
*
|
|
412
|
+
* Two repairs, because the right one differs:
|
|
413
|
+
* - Our own `/download/<id>` capability links (skapi-mcp file-download.js) are
|
|
414
|
+
* base64url, optionally with a single `.` separating the payload and hmac of
|
|
415
|
+
* the older self-describing token. That alphabet cannot contain whitespace,
|
|
416
|
+
* so the spaces are purely damage and REMOVING them restores the exact link,
|
|
417
|
+
* which is what makes an already-sent message clickable again. A model
|
|
418
|
+
* reproducing one of these into its reply is exactly where the spaces come
|
|
419
|
+
* from, which is also why the id is now short.
|
|
420
|
+
* - Anything else keeps every character and only has the whitespace encoded,
|
|
421
|
+
* the same thing a browser does with a space in an href. Stripping would be
|
|
422
|
+
* wrong there: `…/exports/my report.csv` is a real file whose name has a
|
|
423
|
+
* space in it, and deleting it points at a file that does not exist.
|
|
424
|
+
*/
|
|
425
|
+
declare function repairUrlWhitespace(href: string): string;
|
|
426
|
+
/**
|
|
427
|
+
* Trim punctuation and unmatched wrappers that cling to a token in prose.
|
|
428
|
+
* `src::a/b.pdf).` -> `src::a/b.pdf`, while a balanced `file (v2).pdf` is kept.
|
|
429
|
+
*/
|
|
430
|
+
declare function normalizeTrailingInlineToken(value: string): string;
|
|
431
|
+
/** A link the view renders. `expired` means the href is the `_expired_.url`
|
|
432
|
+
* placeholder and a click must mint a fresh one from `remotePath`. */
|
|
433
|
+
interface InlineLinkPart {
|
|
434
|
+
type: 'link';
|
|
435
|
+
label: string;
|
|
436
|
+
fullLabel: string;
|
|
437
|
+
href: string;
|
|
438
|
+
expired: boolean;
|
|
439
|
+
expiredHref?: string;
|
|
440
|
+
remotePath?: string;
|
|
441
|
+
}
|
|
442
|
+
interface InlineLinkContext {
|
|
443
|
+
/** Current project id: the leading segment to strip off a db url. */
|
|
444
|
+
serviceId: string;
|
|
445
|
+
/** `https://db.<hostDomain>` for this deployment. */
|
|
446
|
+
dbHostPrefix: string;
|
|
447
|
+
/** A fresh url already minted for this placeholder, if the view cached one. */
|
|
448
|
+
resolveFreshHref?: (expiredHref: string) => string | undefined;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Decide what ONE inline-link regex match actually is, and how to render it.
|
|
452
|
+
*
|
|
453
|
+
* This is the single place that answers "is this an external url, this project's
|
|
454
|
+
* db file, or a bare storage path", for every consumer. It used to live twice,
|
|
455
|
+
* once in agent.vue and once in the widget, and both copies had to be found and
|
|
456
|
+
* corrected for each of the link bugs this file's history records. A view now
|
|
457
|
+
* supplies its own context (project id, db host, cached-href lookup) and does
|
|
458
|
+
* nothing but turn the returned part into markup.
|
|
459
|
+
*
|
|
460
|
+
* `groups` is [g1..g6] from createInlineLinkRegex, in that order:
|
|
461
|
+
* g1 src::<token> g2/g3 [label](url) g4/g5 [label](path) g6 bare url
|
|
462
|
+
*/
|
|
463
|
+
declare function classifyInlineLink(full: string, groups: Array<string | undefined>, ctx: InlineLinkContext): {
|
|
464
|
+
part: InlineLinkPart;
|
|
465
|
+
tail?: string;
|
|
466
|
+
} | null;
|
|
326
467
|
declare function truncateLabelForDisplay(label: string): string;
|
|
327
468
|
|
|
328
469
|
declare function filterListByClearHorizon(list: any[], clearedAt: number): any[];
|
|
@@ -332,16 +473,107 @@ type MapHistoryOptions = {
|
|
|
332
473
|
clearedAt: number;
|
|
333
474
|
serviceId: string;
|
|
334
475
|
/** View-side display formatter for "Indexing:/Reindexing: …" bubbles. */
|
|
335
|
-
formatIndexingLabel: (name: string, mime?: string, size?: number | null, storagePath?: string) => string;
|
|
476
|
+
formatIndexingLabel: (name: string, mime?: string, size?: number | null, storagePath?: string, reindex?: boolean, continued?: boolean) => string;
|
|
336
477
|
};
|
|
337
478
|
declare function mapHistoryListToMessages(list: any[], platform: 'claude' | 'openai', opts: MapHistoryOptions): {
|
|
338
479
|
messages: any[];
|
|
339
480
|
runningItemIds: string[];
|
|
340
481
|
};
|
|
341
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Keep older history REACHABLE by paging until the message box actually gains
|
|
485
|
+
* something to scroll to.
|
|
486
|
+
*
|
|
487
|
+
* Older history is paged in by one trigger only: the user scrolling to the top
|
|
488
|
+
* of the message box. That trigger has two ways to die, and collapsed indexing
|
|
489
|
+
* rows cause both:
|
|
490
|
+
*
|
|
491
|
+
* 1. The box never scrolls. A file's every indexing pass (the first plus every
|
|
492
|
+
* CONTINUE pass, request AND response bubble each) folds into ONE row, so a
|
|
493
|
+
* full history page — twenty-plus messages — can render as a single line.
|
|
494
|
+
* Content shorter than the viewport fires no scroll event, so page 2 is
|
|
495
|
+
* never requested and any conversation the user had before that upload is
|
|
496
|
+
* permanently out of reach.
|
|
497
|
+
* 2. The fetched page adds no height. A page that is entirely the same file's
|
|
498
|
+
* earlier passes joins the collapsed row already on screen and renders
|
|
499
|
+
* nothing new. The user, sitting at scrollTop 0, scrolls up again — and
|
|
500
|
+
* because the position never changed, no further scroll event fires.
|
|
501
|
+
*
|
|
502
|
+
* Both are the same shape: fetch, re-measure, and keep going until the user
|
|
503
|
+
* genuinely gained reachable content, history ran out, or the pager stopped
|
|
504
|
+
* advancing. `isSatisfied` is what differs between the two (can the box scroll
|
|
505
|
+
* at all / did it grow), so the loop below takes it as a predicate.
|
|
506
|
+
*
|
|
507
|
+
* DOM-free like the rest of the engine — the caller supplies the measurement and
|
|
508
|
+
* awaits its own render before measuring, so agent.vue and the widget run the
|
|
509
|
+
* identical loop over their own pagers.
|
|
510
|
+
*/
|
|
511
|
+
/** Overflow (px) that counts as "the user can scroll here". Comfortably more
|
|
512
|
+
* than the 60px top threshold that triggers the next page, so a filled box has
|
|
513
|
+
* real room to scroll rather than sitting one pixel from the trigger. */
|
|
514
|
+
declare const HISTORY_FILL_SLACK_PX = 64;
|
|
515
|
+
/** Pages one fill pass will request before giving up. Reached only by a chat
|
|
516
|
+
* whose history really is dozens of pages of one file's indexing passes; the
|
|
517
|
+
* cap exists so a pager that stops advancing can never spin forever. */
|
|
518
|
+
declare const MAX_HISTORY_FILL_PAGES = 24;
|
|
519
|
+
type FillHistoryViewportOptions = {
|
|
520
|
+
/** The user has reachable content and paging can stop. Called AFTER the
|
|
521
|
+
* caller's own render has settled (nextTick / rAF), since only the caller
|
|
522
|
+
* knows when its view has painted — hence the allowance for a promise. */
|
|
523
|
+
isSatisfied: () => boolean | Promise<boolean>;
|
|
524
|
+
/** All history is loaded — nothing left to page in. */
|
|
525
|
+
isEndOfList: () => boolean;
|
|
526
|
+
/** A history request is already in flight. Waited out, not treated as a stop
|
|
527
|
+
* condition: a background first-page refresh (the queue-detect tick fires one
|
|
528
|
+
* every couple of seconds while a file is indexing) would otherwise swallow
|
|
529
|
+
* the user's scroll-up entirely, and scrolling up again from scrollTop 0
|
|
530
|
+
* produces no second event to retry with. */
|
|
531
|
+
isLoading: () => boolean;
|
|
532
|
+
/** Messages currently loaded. Used to detect a page that added nothing, which
|
|
533
|
+
* means the pager is not advancing and looping would never terminate. */
|
|
534
|
+
messageCount: () => number;
|
|
535
|
+
/** Fetch ONE older page (the caller's own fetchMore path, scroll-restore and
|
|
536
|
+
* all). Return `false` when the request was NOT issued (the caller's own
|
|
537
|
+
* single-flight guard swallowed it) so the loop retries instead of reading
|
|
538
|
+
* the unchanged message count as an exhausted pager. Anything else, including
|
|
539
|
+
* undefined, means it was attempted. */
|
|
540
|
+
fetchOlder: () => Promise<boolean | void | any>;
|
|
541
|
+
/** The chat this fill was started for is gone (project switched, view
|
|
542
|
+
* unmounted, gate token bumped). Checked between pages so a stale fill can
|
|
543
|
+
* never keep paging another chat's history. */
|
|
544
|
+
isStale?: () => boolean;
|
|
545
|
+
maxPages?: number;
|
|
546
|
+
};
|
|
547
|
+
/**
|
|
548
|
+
* Page older history until `isSatisfied`, until history runs out, or until the
|
|
549
|
+
* pager stops advancing. Never throws: a failed page ends the fill, and the
|
|
550
|
+
* user's own scrolling remains the fallback trigger.
|
|
551
|
+
*/
|
|
552
|
+
declare function fillHistoryViewport(opts: FillHistoryViewportOptions): Promise<void>;
|
|
553
|
+
/**
|
|
554
|
+
* One fill loop per view, with predicates COMBINED rather than dropped.
|
|
555
|
+
*
|
|
556
|
+
* Fills come from several places at once — a first page finishing, a window
|
|
557
|
+
* resize, a row being collapsed, and the user's own scroll to the top — and a
|
|
558
|
+
* plain "one at a time, drop the rest" guard picks the wrong winner: a resize
|
|
559
|
+
* fill (satisfied the moment the box can scroll at all) would swallow the user's
|
|
560
|
+
* scroll-up (which needs content specifically ABOVE them), and the scroll-up
|
|
561
|
+
* cannot be retried, because a reader parked at scrollTop 0 produces no further
|
|
562
|
+
* scroll event. Dropping the guard entirely is no better: every frame of a
|
|
563
|
+
* window drag would start its own 24-page loop.
|
|
564
|
+
*
|
|
565
|
+
* So a request that arrives mid-loop ANDs its predicate into the running one:
|
|
566
|
+
* the loop then keeps paging until EVERY caller is satisfied. Predicates that
|
|
567
|
+
* come true are dropped as it goes, so the cost stays flat.
|
|
568
|
+
*/
|
|
569
|
+
declare function createHistoryFiller(base: Omit<FillHistoryViewportOptions, 'isSatisfied'>): {
|
|
570
|
+
fill: (isSatisfied: () => boolean | Promise<boolean>) => Promise<void>;
|
|
571
|
+
isRunning: () => boolean;
|
|
572
|
+
};
|
|
573
|
+
|
|
342
574
|
declare const MCP_NAME = "BunnyQuery";
|
|
343
575
|
declare const DEFAULT_CLAUDE_MODEL = "claude-sonnet-4-6";
|
|
344
|
-
declare const DEFAULT_OPENAI_MODEL = "gpt-5.
|
|
576
|
+
declare const DEFAULT_OPENAI_MODEL = "gpt-5.6-luna";
|
|
345
577
|
type ClaudeRole = 'user' | 'assistant';
|
|
346
578
|
type ClaudeMessage = {
|
|
347
579
|
role: ClaudeRole;
|
|
@@ -423,6 +655,16 @@ declare function extractOpenAIText(response: any): any;
|
|
|
423
655
|
declare function listClaudeModels(service: string, owner: string): Promise<any>;
|
|
424
656
|
declare function listOpenAIModels(service: string, owner: string): Promise<any>;
|
|
425
657
|
declare const BG_INDEXING_QUEUE_SUFFIX = "-bg";
|
|
658
|
+
/**
|
|
659
|
+
* True when a request belongs to the background-indexing queue.
|
|
660
|
+
*
|
|
661
|
+
* Accepts BOTH shapes this value arrives in: the bare queue name the client sends
|
|
662
|
+
* ("<userId>-bg"), and the server qid that comes back on history/poll responses
|
|
663
|
+
* ("<service>:<queue>|<seq>"). Testing the tail of the raw value only works for the
|
|
664
|
+
* first: a qid ends in "|<seq>", so `endsWith('-bg')` is always false for it — which
|
|
665
|
+
* silently meant history items were NEVER recognised as background tasks.
|
|
666
|
+
*/
|
|
667
|
+
declare function isBgIndexingQueue(queueName?: string): boolean;
|
|
426
668
|
type BgTaskEntry = {
|
|
427
669
|
serviceId: string;
|
|
428
670
|
platform: 'claude' | 'openai';
|
|
@@ -464,6 +706,35 @@ interface ChatIdentity {
|
|
|
464
706
|
serviceName?: string;
|
|
465
707
|
serviceDescription?: string;
|
|
466
708
|
}
|
|
709
|
+
/**
|
|
710
|
+
* Project context captured at the moment the user hit Send, so a turn whose
|
|
711
|
+
* dispatch is delayed (attachment uploads are awaited first) still reaches the
|
|
712
|
+
* project the question was asked of rather than whichever project the user has
|
|
713
|
+
* navigated to by then. Both fields are identity-derived and must be snapshotted
|
|
714
|
+
* together — the system prompt embeds the service name/description/id.
|
|
715
|
+
*/
|
|
716
|
+
interface PinnedDispatchContext {
|
|
717
|
+
identity: ChatIdentity;
|
|
718
|
+
systemPrompt: string;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* The file a background-indexing bubble belongs to. Stamped on the REQUEST
|
|
722
|
+
* bubble (both the live one and the one rebuilt from history) so the display
|
|
723
|
+
* layer can group a file's many passes without reverse-parsing the view's
|
|
724
|
+
* formatted label. See indexing_groups.buildChatDisplayList.
|
|
725
|
+
*/
|
|
726
|
+
interface IndexingFileRef {
|
|
727
|
+
name: string;
|
|
728
|
+
/** Storage path, when known. Preferred group identity: a file can be
|
|
729
|
+
* re-uploaded under a name that already exists elsewhere. */
|
|
730
|
+
path?: string;
|
|
731
|
+
mime?: string;
|
|
732
|
+
size?: number;
|
|
733
|
+
isReindex?: boolean;
|
|
734
|
+
/** A CONTINUE pass. Its absence across every loaded pass is what tells the
|
|
735
|
+
* display layer that earlier passes are still unpaged. */
|
|
736
|
+
continued?: boolean;
|
|
737
|
+
}
|
|
467
738
|
interface ChatMessage {
|
|
468
739
|
role: 'user' | 'assistant';
|
|
469
740
|
content: string;
|
|
@@ -475,11 +746,14 @@ interface ChatMessage {
|
|
|
475
746
|
isCancelled?: boolean;
|
|
476
747
|
isError?: boolean;
|
|
477
748
|
isBackgroundTask?: boolean;
|
|
749
|
+
/** Set on background-indexing REQUEST bubbles only (see IndexingFileRef). */
|
|
750
|
+
_indexFile?: IndexingFileRef;
|
|
478
751
|
_useBgQueue?: boolean;
|
|
479
752
|
_serverItemId?: string;
|
|
480
753
|
_localId?: string;
|
|
481
754
|
_cancelling?: boolean;
|
|
482
755
|
_cancelError?: string;
|
|
756
|
+
_ownerKey?: string;
|
|
483
757
|
}
|
|
484
758
|
interface ChatState {
|
|
485
759
|
messages: ChatMessage[];
|
|
@@ -509,6 +783,12 @@ interface ChatHost {
|
|
|
509
783
|
scrollToBottom(smooth?: boolean): Promise<void> | void;
|
|
510
784
|
/** Scroll only if the user is pinned to the bottom (does not force-pin). */
|
|
511
785
|
scrollToBottomIfSticky(smooth?: boolean): Promise<void> | void;
|
|
786
|
+
/** A history page finished loading and rendering. OPTIONAL. The view uses it
|
|
787
|
+
* to page further when the message box came out too short to scroll — the
|
|
788
|
+
* only trigger for loading older history is a scroll to the top, so a box
|
|
789
|
+
* that cannot scroll has no way to reach page 2 (see viewport_fill). Only
|
|
790
|
+
* the view can measure that, which is why the engine merely announces it. */
|
|
791
|
+
onHistoryLoaded?(fetchMore: boolean, token: number): void;
|
|
512
792
|
cancelRequest(opts: {
|
|
513
793
|
url: string;
|
|
514
794
|
method: string;
|
|
@@ -522,7 +802,7 @@ interface ChatHost {
|
|
|
522
802
|
} | any>;
|
|
523
803
|
refreshSession(): Promise<boolean>;
|
|
524
804
|
/** Build the "Indexing:/Reindexing: …" label (view-side display formatting). */
|
|
525
|
-
formatIndexingLabel(name: string, mime?: string, size?: number | null, storagePath?: string, reindex?: boolean): string;
|
|
805
|
+
formatIndexingLabel(name: string, mime?: string, size?: number | null, storagePath?: string, reindex?: boolean, continued?: boolean): string;
|
|
526
806
|
/** drainBgTaskQueue is a no-op until the chat view is mounted. */
|
|
527
807
|
isViewMounted(): boolean;
|
|
528
808
|
/** Clear-horizon timestamp (localStorage, per service#platform) — view-owned. */
|
|
@@ -556,6 +836,105 @@ interface ChatHost {
|
|
|
556
836
|
updateComposerControls(): void;
|
|
557
837
|
}
|
|
558
838
|
|
|
839
|
+
/**
|
|
840
|
+
* Background file-indexing turns, collapsed into ONE row per file.
|
|
841
|
+
*
|
|
842
|
+
* A single upload can produce many chat turns: the first "Indexing: <file>" pass
|
|
843
|
+
* plus up to MAX_INDEXING_RESUME_PASSES CONTINUE passes, each with its own
|
|
844
|
+
* request AND response bubble. Rendered flat, that reads as the same task
|
|
845
|
+
* repeating forever, and any real question the user asks in between gets buried.
|
|
846
|
+
*
|
|
847
|
+
* buildChatDisplayList turns the flat message array into a DISPLAY list in which
|
|
848
|
+
* every message belonging to one file (however far apart they sit, and whatever
|
|
849
|
+
* else is interleaved between them) is represented by a single group entry,
|
|
850
|
+
* rendered at the position of that file's NEWEST turn. Newest, not oldest, so:
|
|
851
|
+
* - a running index sits at the bottom, where the activity is, and
|
|
852
|
+
* - paging older history in never moves a row that is already on screen
|
|
853
|
+
* (older passes simply join the group they already belong to).
|
|
854
|
+
*
|
|
855
|
+
* The group deliberately reports no authoritative pass TOTAL. History is paged
|
|
856
|
+
* newest-first, so any total computed from loaded messages is a lower bound that
|
|
857
|
+
* a later scroll-up would contradict. It reports STATE (indexing / indexed /
|
|
858
|
+
* failed), how many passes are currently loaded, and `mayHaveOlder` when the
|
|
859
|
+
* file's first pass is not among them.
|
|
860
|
+
*
|
|
861
|
+
* Pure and view-agnostic: agent.vue and the BunnyQuery widget both render from
|
|
862
|
+
* this, so the two stay identical.
|
|
863
|
+
*/
|
|
864
|
+
|
|
865
|
+
type IndexingGroupStatus = 'active' | 'done' | 'error' | 'cancelled';
|
|
866
|
+
type IndexingGroup = {
|
|
867
|
+
/** The FILE this row is about: storage path when known (a file can be
|
|
868
|
+
* re-uploaded under a name that already exists elsewhere), else name. Shared
|
|
869
|
+
* by every run of that file, and what ChatSession.cancelIndexingGroup and
|
|
870
|
+
* _indexKeyOf match on — never use it as a render key. */
|
|
871
|
+
key: string;
|
|
872
|
+
/** Identity of this ROW: one indexing RUN of that file. A file indexed on
|
|
873
|
+
* Monday and re-indexed on Wednesday is two runs, and collapsing them into
|
|
874
|
+
* one row erased Monday's from Monday's place in the conversation, claimed
|
|
875
|
+
* its passes for Wednesday, and let Monday's failure be overwritten by
|
|
876
|
+
* Wednesday's success. Numbered from the NEWEST run backwards (`#0` is the
|
|
877
|
+
* newest) so paging in older history never renames a row already on screen.
|
|
878
|
+
* This is the render key and the expansion key. */
|
|
879
|
+
runKey: string;
|
|
880
|
+
name: string;
|
|
881
|
+
path?: string;
|
|
882
|
+
mime?: string;
|
|
883
|
+
size?: number;
|
|
884
|
+
/** True when any loaded pass was a re-index of an already-stored file. */
|
|
885
|
+
isReindex: boolean;
|
|
886
|
+
/** Every message of this file, in chat order, with its index in the source
|
|
887
|
+
* array (so cancel/typewriter paths keep addressing the real message). */
|
|
888
|
+
members: {
|
|
889
|
+
msg: ChatMessage;
|
|
890
|
+
index: number;
|
|
891
|
+
}[];
|
|
892
|
+
/** Indexing passes LOADED (request bubbles), never a server-side total. */
|
|
893
|
+
passCount: number;
|
|
894
|
+
status: IndexingGroupStatus;
|
|
895
|
+
/** Server item ids of the passes that are still queued/running, so the row can
|
|
896
|
+
* offer a stop button (ChatSession.cancelIndexingGroup cancels each). Empty
|
|
897
|
+
* when nothing is cancellable — a finished file, or a live pass whose server
|
|
898
|
+
* id has not come back yet. */
|
|
899
|
+
cancellableIds: string[];
|
|
900
|
+
/** A cancel request is in flight for one of the passes. */
|
|
901
|
+
cancelling: boolean;
|
|
902
|
+
/** Why the last cancel attempt failed (e.g. the pass had already finished). */
|
|
903
|
+
cancelError?: string;
|
|
904
|
+
/** The file's first pass is not among the loaded messages, so earlier passes
|
|
905
|
+
* exist in history that has not been paged in yet. */
|
|
906
|
+
mayHaveOlder: boolean;
|
|
907
|
+
/** Position in the source array this collapsed row renders at. */
|
|
908
|
+
anchorIndex: number;
|
|
909
|
+
};
|
|
910
|
+
type DisplayEntry = {
|
|
911
|
+
kind: 'message';
|
|
912
|
+
msg: ChatMessage;
|
|
913
|
+
index: number;
|
|
914
|
+
} | {
|
|
915
|
+
kind: 'indexing';
|
|
916
|
+
group: IndexingGroup;
|
|
917
|
+
index: number;
|
|
918
|
+
};
|
|
919
|
+
type BuildDisplayListOptions = {
|
|
920
|
+
/** True while older history remains unpaged, which is what makes a group
|
|
921
|
+
* with no first pass genuinely incomplete rather than merely odd. */
|
|
922
|
+
hasMoreHistory?: boolean;
|
|
923
|
+
};
|
|
924
|
+
declare function parseIndexingLabel(content: string): {
|
|
925
|
+
name: string;
|
|
926
|
+
path?: string;
|
|
927
|
+
continued: boolean;
|
|
928
|
+
isReindex: boolean;
|
|
929
|
+
} | null;
|
|
930
|
+
/**
|
|
931
|
+
* Collapse background-indexing turns into per-file groups.
|
|
932
|
+
*
|
|
933
|
+
* Messages that are not background-indexing pass through untouched, at their
|
|
934
|
+
* original positions and with their original indices.
|
|
935
|
+
*/
|
|
936
|
+
declare function buildChatDisplayList(messages: ChatMessage[], opts?: BuildDisplayListOptions): DisplayEntry[];
|
|
937
|
+
|
|
559
938
|
/**
|
|
560
939
|
* ChatSession — framework-agnostic stateful chat orchestration.
|
|
561
940
|
*
|
|
@@ -576,33 +955,126 @@ interface ChatHost {
|
|
|
576
955
|
* session via the public methods and re-renders in host.notify().
|
|
577
956
|
*/
|
|
578
957
|
|
|
958
|
+
/** A live poll registered in ChatSession.historyItemPolls. */
|
|
959
|
+
type PollHandle = {
|
|
960
|
+
/** 'bg' = background indexing, pausable. 'fg' = a reply the user is waiting on. */
|
|
961
|
+
kind: 'fg' | 'bg';
|
|
962
|
+
/** Absent on an older skapi-js that cannot stop an attached poll. */
|
|
963
|
+
stop?: () => void;
|
|
964
|
+
};
|
|
579
965
|
declare class ChatSession {
|
|
580
966
|
host: ChatHost;
|
|
581
967
|
state: ChatState;
|
|
582
968
|
bgTaskQueue: BgTaskEntry[];
|
|
583
969
|
cancelledServerIds: Set<string>;
|
|
970
|
+
/** Files whose indexing the user stopped, keyed exactly as buildChatDisplayList
|
|
971
|
+
* keys a group (storage path, else filename). Cancelling one pass is not
|
|
972
|
+
* enough on its own: the client dispatches CONTINUE passes for paged files, so
|
|
973
|
+
* without this the next pass is enqueued the moment the cancelled one settles.
|
|
974
|
+
* Cleared when a FIRST pass for the same file is queued again (a re-upload or a
|
|
975
|
+
* Reindex from the file manager), so stopping a file never poisons it. */
|
|
976
|
+
cancelledIndexKeys: Set<string>;
|
|
584
977
|
pendingAgentRequests: Record<string, Promise<any>>;
|
|
585
978
|
aiChatHistoryCache: Record<string, {
|
|
586
979
|
messages: ChatMessage[];
|
|
587
980
|
endOfList: boolean;
|
|
588
981
|
startKeyHistory: string[];
|
|
589
982
|
}>;
|
|
590
|
-
historyItemPolls: Map<string,
|
|
983
|
+
historyItemPolls: Map<string, PollHandle>;
|
|
984
|
+
/** Non-empty while polling is paused; keyed by reason so overlapping causes
|
|
985
|
+
* (view detached AND tab hidden) do not resume each other prematurely. */
|
|
986
|
+
private _pauseReasons;
|
|
987
|
+
private _resuming;
|
|
591
988
|
private _lidSeq;
|
|
592
989
|
constructor(host: ChatHost);
|
|
990
|
+
/**
|
|
991
|
+
* Register a live poll so (a) a remount dedupes against it instead of stacking a
|
|
992
|
+
* SECOND poll on the same item, and (b) pausePolling can stop it.
|
|
993
|
+
*
|
|
994
|
+
* `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
|
|
995
|
+
* poll simply cannot be stopped and is left running — see pausePolling.
|
|
996
|
+
*/
|
|
997
|
+
private _trackPoll;
|
|
998
|
+
/**
|
|
999
|
+
* Stop and forget one item's poll. Used after a cancel: the row is either gone
|
|
1000
|
+
* (cancelled while queued) or flagged cancelled (cancelled while running), so
|
|
1001
|
+
* asking about it again only burns requests. Safe when no poll is attached, and
|
|
1002
|
+
* safe on an older skapi-js with no stop handle (the entry is then LEFT in the
|
|
1003
|
+
* map so a later drain cannot stack a second, unstoppable poll on the id).
|
|
1004
|
+
*/
|
|
1005
|
+
private _stopPoll;
|
|
1006
|
+
/** True while any pause reason is active. */
|
|
1007
|
+
isPollingPaused(): boolean;
|
|
1008
|
+
/**
|
|
1009
|
+
* Stop BACKGROUND polling until resumePolling. Foreground polls (a reply the user is
|
|
1010
|
+
* waiting on) keep running deliberately: their results must still land in the history
|
|
1011
|
+
* cache so resumePendingRequest can render them on return, otherwise a user who sends
|
|
1012
|
+
* a message then navigates away comes back to a permanently stuck "Thinking...".
|
|
1013
|
+
*
|
|
1014
|
+
* Server-side work is untouched; this only stops asking about it. That is safe for
|
|
1015
|
+
* document indexing because the worker drives that loop itself.
|
|
1016
|
+
*/
|
|
1017
|
+
pausePolling(reason: string): void;
|
|
1018
|
+
/**
|
|
1019
|
+
* Lift a pause reason WITHOUT running the reconcile. For a caller that is about to
|
|
1020
|
+
* reload history anyway (a view remounting), letting resumePolling also reconcile
|
|
1021
|
+
* would race that load and can double-attach.
|
|
1022
|
+
*/
|
|
1023
|
+
clearPauseReason(reason: string): void;
|
|
1024
|
+
/**
|
|
1025
|
+
* Clear a pause reason and, once none remain, re-attach polling and reconcile.
|
|
1026
|
+
* Deliberately does NOT touch gateRefreshToken: bumping it would silently discard
|
|
1027
|
+
* the results of anything still in flight across the pause.
|
|
1028
|
+
*/
|
|
1029
|
+
resumePolling(reason: string): Promise<void>;
|
|
593
1030
|
private _newLocalId;
|
|
594
1031
|
getHistoryCacheKey(): string;
|
|
595
1032
|
updateHistoryCache(): void;
|
|
1033
|
+
/**
|
|
1034
|
+
* Land a resolved reply in the history cache of a chat that is NOT currently
|
|
1035
|
+
* visible, without touching state.messages. Mirrors the cache-only path in
|
|
1036
|
+
* dispatchAgentRequest: REPLACE the trailing pending "Thinking..." bubble
|
|
1037
|
+
* (append only when there is none), and settle the matching pending user
|
|
1038
|
+
* bubble, so the cached copy never keeps a stuck "Thinking..." that a later
|
|
1039
|
+
* cache-first load would re-render forever.
|
|
1040
|
+
*/
|
|
1041
|
+
private _applyReplyToCache;
|
|
1042
|
+
/**
|
|
1043
|
+
* serviceId/owner are passed explicitly by every caller: a request can be
|
|
1044
|
+
* dispatched after the user moved to another project, and re-reading the live
|
|
1045
|
+
* identity here would silently send the turn to THAT project instead of the
|
|
1046
|
+
* one it was composed for. Falls back to the live read only when a caller
|
|
1047
|
+
* omits them.
|
|
1048
|
+
*/
|
|
596
1049
|
private _callProviderFor;
|
|
597
1050
|
dispatchAgentRequest(params: any): Promise<any>;
|
|
598
|
-
dispatchComposedMessage(composed: string, useBgQueue?: boolean, composedForLlm?: string, extractContent?: any, fileUrls?: any): void;
|
|
1051
|
+
dispatchComposedMessage(composed: string, useBgQueue?: boolean, composedForLlm?: string, extractContent?: any, fileUrls?: any, pinned?: PinnedDispatchContext): void;
|
|
599
1052
|
promoteNextBgQueuedToRunning(): void;
|
|
600
1053
|
promoteNextQueuedToRunning(): void;
|
|
601
1054
|
resolveQueuedUserBubble(serverId?: string): number | undefined;
|
|
602
1055
|
insertAtTarget(msg: ChatMessage, targetIdx: number): void;
|
|
603
|
-
onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string): void;
|
|
604
|
-
onQueuedSendError(_composed: string, err: any, serverId?: string): void;
|
|
1056
|
+
onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string, ownerKey?: string): void;
|
|
1057
|
+
onQueuedSendError(_composed: string, err: any, serverId?: string, ownerKey?: string): void;
|
|
605
1058
|
cancelQueuedMessage(msg: ChatMessage, idx: number): void;
|
|
1059
|
+
/**
|
|
1060
|
+
* Stop indexing a file, from its collapsed row — every pass at once, not just
|
|
1061
|
+
* the bubble the user happens to see.
|
|
1062
|
+
*
|
|
1063
|
+
* A big file is indexed as a CHAIN of passes, so cancelling only the live one
|
|
1064
|
+
* accomplishes nothing: the next pass is dispatched as soon as it settles.
|
|
1065
|
+
* Three things end the chain:
|
|
1066
|
+
* 1. every queued/running pass of this file is cancelled server-side
|
|
1067
|
+
* (csr-cancel deletes a queued row and flags a running one "cancelled",
|
|
1068
|
+
* which is also the worker's gate for NOT enqueueing the next window);
|
|
1069
|
+
* 2. the file is remembered in cancelledIndexKeys, so the client-driven
|
|
1070
|
+
* resume (maybeResumeIndexing) stops dispatching CONTINUE passes; and
|
|
1071
|
+
* 3. any of its passes still sitting in bgTaskQueue is dropped by the next
|
|
1072
|
+
* drain rather than surfacing a fresh "Indexing…" bubble.
|
|
1073
|
+
*
|
|
1074
|
+
* Records already written by the passes that DID run are kept — this stops the
|
|
1075
|
+
* work, it does not undo it.
|
|
1076
|
+
*/
|
|
1077
|
+
cancelIndexingGroup(group: IndexingGroup): void;
|
|
606
1078
|
typewriteIntoIndex(idx: number, fullText: string, localId?: string): Promise<void>;
|
|
607
1079
|
private typewriterQueue;
|
|
608
1080
|
enqueueTypewrite(idx: number, fullText: string, localId?: string): Promise<any>;
|
|
@@ -612,6 +1084,39 @@ declare class ChatSession {
|
|
|
612
1084
|
resumePendingRequest(token: number): Promise<void>;
|
|
613
1085
|
handleHistoryItemResolution(itemId: string, response: any, platform: string): void;
|
|
614
1086
|
applyHistoryItemResolution(itemId: string, response: any, platform: string): void;
|
|
1087
|
+
/** How a bg task maps onto a collapsed row: the row's own key (storage path
|
|
1088
|
+
* when known, else the filename), scoped to the chat it belongs to. A storage
|
|
1089
|
+
* path is project-relative ("report.xlsx"), and ONE ChatSession serves every
|
|
1090
|
+
* project — unscoped, stopping a file in one project would silently suppress
|
|
1091
|
+
* the same filename's continuations in another. */
|
|
1092
|
+
private _indexKeyOf;
|
|
1093
|
+
/**
|
|
1094
|
+
* Reconcile the bg queue with the files the user has stopped.
|
|
1095
|
+
*
|
|
1096
|
+
* A FIRST pass (no resumePass) is a fresh indexing request — a re-upload, or a
|
|
1097
|
+
* Reindex from the file manager — so it LIFTS the stop: the key is a storage
|
|
1098
|
+
* path, and without this an earlier cancel would silently kill every future
|
|
1099
|
+
* index of the same path. A continuation of a stopped file is dropped instead,
|
|
1100
|
+
* covering the pass that was dispatched in the moment before the cancel landed.
|
|
1101
|
+
*/
|
|
1102
|
+
private _applyIndexCancellations;
|
|
1103
|
+
/**
|
|
1104
|
+
* Cancel any live pass of a stopped file that turned up on its own.
|
|
1105
|
+
*
|
|
1106
|
+
* The client is not the only thing that continues a file: for PDFs and (when
|
|
1107
|
+
* windowed indexing is on) text/grid files the WORKER enqueues the next window
|
|
1108
|
+
* itself, and that pass reaches the chat through the history poll, never
|
|
1109
|
+
* through bgTaskQueue. The worker's own gate stops the chain when the running
|
|
1110
|
+
* row is cancelled — but if the row had already finished when the user hit
|
|
1111
|
+
* stop, the next window was queued a moment earlier and still arrives. Stop it
|
|
1112
|
+
* here rather than making the user hit stop again.
|
|
1113
|
+
*
|
|
1114
|
+
* Runs from drainBgTaskQueue, which both clients call after a history load.
|
|
1115
|
+
*/
|
|
1116
|
+
private _sweepCancelledIndexing;
|
|
1117
|
+
/** Best-effort server-side cancel of a bg-queue item that has no bubble (so
|
|
1118
|
+
* cancelQueuedMessage, which drives one, has nothing to act on). */
|
|
1119
|
+
private _cancelServerItem;
|
|
615
1120
|
drainBgTaskQueue(): void;
|
|
616
1121
|
maybeResumeIndexing(entry: BgTaskEntry, response: any, platform: string): void;
|
|
617
1122
|
loadHistory(fetchMore?: boolean, token?: number): Promise<void>;
|
|
@@ -629,4 +1134,4 @@ declare class ChatSession {
|
|
|
629
1134
|
bumpGate(): void;
|
|
630
1135
|
}
|
|
631
1136
|
|
|
632
|
-
export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, type ExtractDirective, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingSystemPromptParams, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, clearAttachmentParsers, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, parseAttachmentContent, registerAttachmentParser, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
|
|
1137
|
+
export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, type DisplayEntry, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, type PinnedDispatchContext, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAttachmentContent, parseIndexingLabel, readExpiredAttachmentHref, registerAttachmentParser, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
|