bunnyquery 1.5.7 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bunnyquery.js +522 -65
- package/dist/engine.cjs +516 -63
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +160 -5
- package/dist/engine.d.ts +160 -5
- package/dist/engine.mjs +511 -64
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/config.ts +19 -0
- package/src/engine/host.ts +19 -0
- package/src/engine/index.ts +2 -1
- package/src/engine/office.ts +78 -5
- package/src/engine/prompts/index.ts +5 -0
- package/src/engine/prompts/indexing_system_prompt.ts +5 -3
- package/src/engine/prompts/indexing_user_message.ts +159 -0
- package/src/engine/requests.ts +170 -25
- package/src/engine/session.ts +402 -42
package/dist/engine.mjs
CHANGED
|
@@ -59,6 +59,9 @@ function chatEngineConfig() {
|
|
|
59
59
|
}
|
|
60
60
|
return _config;
|
|
61
61
|
}
|
|
62
|
+
function windowedIndexingEnabled() {
|
|
63
|
+
return _config?.windowedIndexing === true;
|
|
64
|
+
}
|
|
62
65
|
function pollOpt() {
|
|
63
66
|
const p = _config?.poll;
|
|
64
67
|
return p === void 0 ? {} : { poll: p };
|
|
@@ -153,19 +156,65 @@ function isServerExtractable(name, mime) {
|
|
|
153
156
|
return m.includes("officedocument") || m.includes("opendocument") || m.includes("hwp") || m.includes("epub") || m === "application/msword" || m === "application/vnd.ms-excel" || m === "application/vnd.ms-powerpoint";
|
|
154
157
|
}
|
|
155
158
|
var isOfficeFile = isServerExtractable;
|
|
156
|
-
var PAGED_READ_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
159
|
+
var PAGED_READ_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
160
|
+
// grids
|
|
161
|
+
"xls",
|
|
162
|
+
"xlsx",
|
|
163
|
+
"xlsm",
|
|
164
|
+
"ods",
|
|
165
|
+
// delimited text (row-windowed by the layer)
|
|
166
|
+
"csv",
|
|
167
|
+
"tsv",
|
|
168
|
+
"tab",
|
|
169
|
+
// documents
|
|
170
|
+
"pdf",
|
|
171
|
+
"docx",
|
|
172
|
+
"pptx",
|
|
173
|
+
// plain text / data / markup
|
|
174
|
+
"txt",
|
|
175
|
+
"md",
|
|
176
|
+
"markdown",
|
|
177
|
+
"log",
|
|
178
|
+
"json",
|
|
179
|
+
"jsonl",
|
|
180
|
+
"ndjson",
|
|
181
|
+
"xml",
|
|
182
|
+
"yaml",
|
|
183
|
+
"yml"
|
|
184
|
+
]);
|
|
157
185
|
function isPagedReadFile(name, mime) {
|
|
158
186
|
const ext = (name || "").split(".").pop()?.toLowerCase() || "";
|
|
159
187
|
if (PAGED_READ_EXTENSIONS.has(ext)) return true;
|
|
160
188
|
const m = (mime || "").toLowerCase();
|
|
161
189
|
return m === "application/pdf" || m === "application/vnd.ms-excel" || m.includes("spreadsheetml") || m.includes("opendocument.spreadsheet");
|
|
162
190
|
}
|
|
191
|
+
function isImageVisionFile(name, mime) {
|
|
192
|
+
const ext = (name || "").split(".").pop()?.toLowerCase() || "";
|
|
193
|
+
return ext === "pdf" || (mime || "").toLowerCase() === "application/pdf";
|
|
194
|
+
}
|
|
163
195
|
var _extractPlaceholderSeq = 0;
|
|
164
196
|
function makeExtractPlaceholder(seed) {
|
|
165
197
|
_extractPlaceholderSeq += 1;
|
|
166
198
|
const slug = (seed || "file").replace(/[^a-zA-Z0-9]+/g, "_").slice(-48);
|
|
167
199
|
return `{{SKAPI_FILE_CONTENT::${slug}-${_extractPlaceholderSeq}}}`;
|
|
168
200
|
}
|
|
201
|
+
var _renderPlaceholderSeq = 0;
|
|
202
|
+
function makeRenderPlaceholder(seed) {
|
|
203
|
+
_renderPlaceholderSeq += 1;
|
|
204
|
+
const slug = (seed || "file").replace(/[^a-zA-Z0-9]+/g, "_").slice(-48);
|
|
205
|
+
return `{{SKAPI_RENDER::${slug}-${_renderPlaceholderSeq}}}`;
|
|
206
|
+
}
|
|
207
|
+
var RENDER_PAGES_PER_WINDOW = 5;
|
|
208
|
+
var _windowPlaceholderSeq = 0;
|
|
209
|
+
function makeWindowPlaceholder(seed) {
|
|
210
|
+
_windowPlaceholderSeq += 1;
|
|
211
|
+
const slug = (seed || "file").replace(/[^a-zA-Z0-9]+/g, "_").slice(-48);
|
|
212
|
+
return `{{SKAPI_WINDOW::${slug}-${_windowPlaceholderSeq}}}`;
|
|
213
|
+
}
|
|
214
|
+
function isWindowedReadFile(name, mime) {
|
|
215
|
+
if (isImageVisionFile(name, mime)) return false;
|
|
216
|
+
return isPagedReadFile(name, mime);
|
|
217
|
+
}
|
|
169
218
|
function composeUserMessage(text, attachmentUrls) {
|
|
170
219
|
let composed = text;
|
|
171
220
|
if (attachmentUrls.length > 0) {
|
|
@@ -263,12 +312,14 @@ function buildIndexingSystemPrompt(params) {
|
|
|
263
312
|
let systemPrompt = `You are a background indexing agent for project ${service}.
|
|
264
313
|
- Image files (.jpg, .jpeg, .png, .gif, .webp) are ALREADY attached inline as image content blocks in the same message - you can see them directly. Do NOT call web_fetch on image URLs; that will fail or return garbage. Just look at the image block and answer.
|
|
265
314
|
- Most files (office documents like .docx/.xlsx/.pptx/.hwp/.hwpx/.ods, and text/data/code files like .csv/.tsv/.json/.xml/.txt/.md and source code) have ALREADY been extracted on the server and included inline in the user message between the "BEGIN FILE CONTENT" / "END FILE CONTENT" markers - read that directly. If the inline content is a "[skapi: ...]" note, the file could not be extracted - index it from its metadata only.
|
|
266
|
-
- BIG
|
|
267
|
-
-
|
|
315
|
+
- BIG SPREADSHEETS / TEXT: the inline content may be only the FIRST part of a large file (it can end with a truncation or "more remains" note). For big spreadsheets and big text/data files READ THE FILE WITH THE readFileContent TOOL: it returns the file ONE WINDOW at a time (spreadsheets as coordinate-tagged grid rows, text as a range of characters). Pass the file's storage path. After each window: datafy it into records and SAVE them, THEN if the window says MORE REMAINS call readFileContent again with the cursor it gives you. Repeat until it says END OF FILE, so the WHOLE file is indexed - never stop after the first window. (Do NOT call readFileContent on a PDF - see the next line.)
|
|
316
|
+
- PDFs (scanned or not): you do NOT read a PDF with a tool or a URL. Its pages are RENDERED and embedded directly in the user message as IMAGE blocks, a WINDOW of pages at a time. LOOK at the embedded page images and datafy every one. The note beside them tells you whether MORE pages remain: if so, save this window's records and stop (a follow-up pass shows the next window automatically); only when the note says it was the LAST window is the PDF fully seen. Do NOT call readFileContent or web_fetch for a PDF.
|
|
317
|
+
- VISION: when the message (a readFileContent window, an embedded PDF page, or an inline attachment) includes IMAGES - scanned/rendered PDF pages, or photos embedded in a spreadsheet next to a row/block - LOOK at them and capture what they show as record data (the reading/values in a scanned table, the part/defect/condition visible in a photo). The image IS part of the data; correlate each photo with its labelled block ("PHOTO A3" markers tie a photo to that grid row).
|
|
268
318
|
- Whatever the file type, use the file's storage path (the "storage path" metadata line) as the "src::" unique_id - never the inline content or a temporary URL.
|
|
269
319
|
- TABULAR data (any spreadsheet - .csv/.tsv/.xlsx/.xls/.ods, or sheet-like rows): you MUST save EVERY data row as its own record (ONE record per row) with that row's actual column values in the record's "data", keyed by the header names, in a dedicated table (e.g. "spreadsheet_rows"). Do NOT summarize, sample only a few rows, or save just file metadata - index the whole sheet, paging through it with readFileContent when it is large. Make MULTIPLE postRecords calls in batches (e.g. 30-50 rows per call) rather than one oversized call. This per-row completeness OVERRIDES brevity. ALSO save one file-level summary record (file name, sheet name(s), column headers, total row count, overall summary) - this is the record that carries the file's "src::" unique_id - and link EVERY per-row record to it via reference (set each row record's reference to that src:: file record; the row records themselves do NOT carry a src:: unique_id). The per-row records AND this reference linkage are BOTH mandatory: the linkage is what lets the whole sheet be found and cleaned up together when the file is re-indexed.
|
|
270
320
|
- EPUB / e-books / long-form books (.epub or any book-length prose, provided inline in reading order with chapter headings preserved): you MUST save ONE record per CHAPTER (or, when chapters are unclear, per major section/topic) in a dedicated table (e.g. "book_chapters") - never collapse the whole book into a single record. Each chapter record's "data" must capture the chapter title plus its order/number AND a substantive summary of that chapter's content (key events, arguments, characters, places, concepts, terms, notable quotes). Apply AS MANY relevant tags as possible to EVERY chapter record (characters, locations, themes, topics, key concepts, key terms, dates, named entities) so the book is easy to SEARCH and cross-reference later - this is the whole point. ALSO save one book-level record (title, author, language, overall summary, chapter list / table of contents, genre/subjects) and link each chapter record to it via reference. This per-chapter completeness OVERRIDES brevity; human-readable summaries only, never raw/binary bytes.
|
|
271
|
-
- This is a background indexing task: do ALL the MCP saving FIRST, never reply mid-task, and never ask the user questions. Always use the MCP tools to save what you learn - be exhaustive about meaning (and, for tabular data, about every row). SAVE AS YOU GO: persist each window's records before reading the next, so progress is never lost. If the file is so large you cannot finish in one turn, still save everything you have read so far
|
|
321
|
+
- This is a background indexing task: do ALL the MCP saving FIRST, never reply mid-task, and never ask the user questions. Always use the MCP tools to save what you learn - be exhaustive about meaning (and, for tabular data, about every row). SAVE AS YOU GO: persist each window's records before reading the next, so progress is never lost. If the file is so large you cannot finish in one turn, still save everything you have read so far; a follow-up pass will automatically continue from where you stopped. Never store raw or binary bytes (base64, blobs); describe them in human-readable text instead.
|
|
322
|
+
- COMPLETION SIGNAL: only when you have fully read and saved the ENTIRE file (for readFileContent files: reached "END OF FILE"; for PDFs: the embedded page-image note said it was the LAST window - with all rows/pages/items saved), end your final message with the token INDEXING_COMPLETE on its own line. If you did NOT finish the whole file (more rows/pages remain), do NOT write that token - leaving it out is how the system knows to run another pass to continue.
|
|
272
323
|
- Only AFTER every save is done, send exactly ONE final message summarizing what you indexed - never just "Indexing complete", and never a raw/base64/binary value or a large pasted dump. Keep it to a few factual sentences or a short markdown bullet list covering: the file name, its content type, each table you wrote to with its record/row count and the key columns/fields or topics captured, and anything that could not be extracted. Follow this shape - Indexed <file name> (<content type>): saved <N> records to <table(s)> capturing <key columns/fields or topics>; could not extract: <gaps, or none>.`;
|
|
273
324
|
if (serviceDescription) {
|
|
274
325
|
systemPrompt += `
|
|
@@ -311,6 +362,74 @@ Read this file with the readFileContent tool, using the storage path above - do
|
|
|
311
362
|
}
|
|
312
363
|
return head + `- temporary URL (fetch this to read the file contents): ${attachment.url}`;
|
|
313
364
|
}
|
|
365
|
+
var RENDER_FROM_TOKEN = "{{RENDER_FROM}}";
|
|
366
|
+
var WINDOW_CURSOR_TOKEN = RENDER_FROM_TOKEN;
|
|
367
|
+
function buildIndexingRenderMessage(attachment, placeholder, renderFrom) {
|
|
368
|
+
const from = Math.max(0, renderFrom || 0);
|
|
369
|
+
if (from > 0) return buildIndexingRenderContinueTemplate(attachment, placeholder, String(from + 1));
|
|
370
|
+
return `A new file has just been uploaded. Index it now.
|
|
371
|
+
|
|
372
|
+
` + buildRenderMeta(attachment) + `
|
|
373
|
+
This is a PDF. Its pages are delivered to you as RENDERED PAGE IMAGES embedded directly in this message (you do NOT need any tool, URL, or web_fetch to see them). You are shown a WINDOW of pages at a time, starting at page ${from + 1}.
|
|
374
|
+
` + buildRenderDatafy(placeholder);
|
|
375
|
+
}
|
|
376
|
+
function buildIndexingRenderContinueTemplate(attachment, placeholder, pageLabel = RENDER_FROM_TOKEN) {
|
|
377
|
+
const src = `src::${attachment.storagePath}`;
|
|
378
|
+
return `CONTINUE indexing a PDF whose previous pass did not finish.
|
|
379
|
+
|
|
380
|
+
` + buildRenderMeta(attachment) + `
|
|
381
|
+
Records for the earlier pages are ALREADY saved (they reference "${src}"). The NEXT window of rendered page images (starting at page ${pageLabel}) is embedded in this message. Datafy each page as before and do NOT re-save pages that are already saved.
|
|
382
|
+
` + buildRenderDatafy(placeholder);
|
|
383
|
+
}
|
|
384
|
+
function buildRenderMeta(attachment) {
|
|
385
|
+
return `File metadata:
|
|
386
|
+
- name: ${attachment.name}
|
|
387
|
+
- storage path: ${attachment.storagePath}
|
|
388
|
+
` + (attachment.mime ? `- mime type: ${attachment.mime}
|
|
389
|
+
` : "");
|
|
390
|
+
}
|
|
391
|
+
function buildRenderDatafy(placeholder) {
|
|
392
|
+
return `
|
|
393
|
+
${placeholder}
|
|
394
|
+
|
|
395
|
+
LOOK at each rendered page image in this message and DATAFY what it shows: for EVERY page call postRecords and save records - one record per row / table entry / line item visible on the page (or one record for the page if it is prose), capturing every value you can read (OCR the text, read tables cell by cell, describe any photos/diagrams). Use the storage path above for the "src::" unique_id.
|
|
396
|
+
|
|
397
|
+
Save records for THIS window of pages only, then stop and report what you saved. Do NOT try to read the rest of the file and do NOT worry about the pages after this window: if any remain, the next window is rendered and sent to you automatically. Report only the pages you were actually shown - never imply you have seen the whole document.`;
|
|
398
|
+
}
|
|
399
|
+
function buildIndexingWindowMessage(attachment, placeholder, isContinuation, positionLabel) {
|
|
400
|
+
const src = `src::${attachment.storagePath}`;
|
|
401
|
+
const head = isContinuation ? `CONTINUE indexing a file whose previous pass did not finish.
|
|
402
|
+
|
|
403
|
+
` : `A new file has just been uploaded. Index it now.
|
|
404
|
+
|
|
405
|
+
`;
|
|
406
|
+
const where = isContinuation ? `
|
|
407
|
+
Records for the earlier windows are ALREADY saved (they reference "${src}"). The NEXT window (starting at ${positionLabel || WINDOW_CURSOR_TOKEN}) is embedded below. Do NOT re-save windows that are already saved.
|
|
408
|
+
` : `
|
|
409
|
+
This file is delivered to you ONE WINDOW at a time, embedded directly in this message. You do NOT need any tool, URL, or web_fetch to read it.
|
|
410
|
+
`;
|
|
411
|
+
return head + buildRenderMeta(attachment) + where + `
|
|
412
|
+
${placeholder}
|
|
413
|
+
|
|
414
|
+
DATAFY this window: call postRecords and save records for everything in it - ONE RECORD PER ROW for tabular data (keyed by the column headers), or one record per section for prose. Capture every value you can read. Use the storage path above for the "src::" unique_id on the file-level record, and link every row/section record to it by reference.
|
|
415
|
+
|
|
416
|
+
Save records for THIS window only, then stop and report what you saved. Do NOT try to read the rest of the file, and do NOT call readFileContent - if more remains, the next window is read and sent to you automatically. Report only what you were actually shown, and never imply you have seen the whole file when the note beside the window says more remains.`;
|
|
417
|
+
}
|
|
418
|
+
function buildIndexingContinueMessage(attachment) {
|
|
419
|
+
const src = `src::${attachment.storagePath}`;
|
|
420
|
+
return `CONTINUE indexing a file whose previous pass did not finish.
|
|
421
|
+
|
|
422
|
+
File metadata:
|
|
423
|
+
- name: ${attachment.name}
|
|
424
|
+
- storage path: ${attachment.storagePath}
|
|
425
|
+
` + (attachment.mime ? `- mime type: ${attachment.mime}
|
|
426
|
+
` : "") + `
|
|
427
|
+
Records for the earlier windows/pages of this file are ALREADY saved (they reference "${src}"). First call getRecords with reference "${src}" to see how far the previous pass got (the furthest page/row/window already saved). Then call readFileContent with the storage path above and a CURSOR that RESUMES just after that point - do NOT start at the beginning. The cursor is derivable from what you already saved:
|
|
428
|
+
- PDF: the cursor is the NUMBER OF PAGES already read (0-based next page). If you saved up to page N, call readFileContent with cursor="N" to get page N+1 onward.
|
|
429
|
+
- Spreadsheet: the cursor is "<sheetIndex>:<nextRow>" (0-based sheet index, 1-based row). If you saved up to row R of sheet S, use cursor="S:R+1".
|
|
430
|
+
- Text: the cursor is the character offset already read.
|
|
431
|
+
Index the REMAINING windows - one record per row/item, looking at any page images or embedded photos - saving as you go until readFileContent reports END OF FILE. Do NOT re-save windows that are already saved. Use the storage path above for the "src::" unique_id. When the ENTIRE file is finally indexed, end your message with the token INDEXING_COMPLETE.`;
|
|
432
|
+
}
|
|
314
433
|
|
|
315
434
|
// src/engine/errors.ts
|
|
316
435
|
function getErrorMessage(input) {
|
|
@@ -567,19 +686,22 @@ var mcpUrl = () => chatEngineConfig().mcpBaseUrl;
|
|
|
567
686
|
var clientSecretRequest = (opts) => chatEngineConfig().clientSecretRequest(opts);
|
|
568
687
|
var getOpenAIImageDetail = (model) => {
|
|
569
688
|
const normalized = (model || DEFAULT_OPENAI_MODEL).trim().toLowerCase();
|
|
570
|
-
const match = normalized.match(/^gpt-(\d+)(?:\.(\d+))?$/);
|
|
689
|
+
const match = normalized.match(/^gpt-(\d+)(?:\.(\d+))?(-[a-z0-9.\-]+)?$/);
|
|
571
690
|
if (!match) {
|
|
572
691
|
return DEFAULT_OPENAI_IMAGE_DETAIL;
|
|
573
692
|
}
|
|
574
693
|
const major = Number(match[1]);
|
|
575
694
|
const minor = match[2] === void 0 ? null : Number(match[2]);
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
return "original";
|
|
695
|
+
const isVariant = !!match[3];
|
|
696
|
+
const supportsOriginal = major > 5 || major === 5 && minor !== null && minor >= 4;
|
|
697
|
+
if (!supportsOriginal) {
|
|
698
|
+
return DEFAULT_OPENAI_IMAGE_DETAIL;
|
|
581
699
|
}
|
|
582
|
-
return
|
|
700
|
+
return isVariant ? "high" : "original";
|
|
701
|
+
};
|
|
702
|
+
var getRenderImageDetail = (model) => {
|
|
703
|
+
const detail = getOpenAIImageDetail(model);
|
|
704
|
+
return detail === DEFAULT_OPENAI_IMAGE_DETAIL ? "high" : detail;
|
|
583
705
|
};
|
|
584
706
|
var IMAGE_URL_REGEX = /\bhttps?:\/\/[^\s<>"'()\[\]]+?\.(?:jpg|jpeg|png|gif|webp)(?:\?[^\s<>"'()\[\]]*)?/gi;
|
|
585
707
|
function transformContentWithImages(content) {
|
|
@@ -815,14 +937,53 @@ async function callOpenAIWithPublicMcp(prompt, service, owner, messages, system,
|
|
|
815
937
|
}
|
|
816
938
|
});
|
|
817
939
|
}
|
|
940
|
+
async function notifyAgentContinueIndexing(info) {
|
|
941
|
+
return notifyAgentSaveAttachment({ ...info, continueIndexing: true });
|
|
942
|
+
}
|
|
818
943
|
async function notifyAgentSaveAttachment(info) {
|
|
819
944
|
const { platform, service, owner, attachment, parsedContent } = info;
|
|
820
|
-
const
|
|
821
|
-
const
|
|
945
|
+
const continuing = !!info.continueIndexing;
|
|
946
|
+
const visionFile = !parsedContent && isImageVisionFile(attachment.name, attachment.mime);
|
|
947
|
+
const renderFrom = Math.max(0, info.renderFrom || 0);
|
|
948
|
+
const renderPlaceholder = visionFile ? makeRenderPlaceholder(attachment.storagePath) : void 0;
|
|
949
|
+
const renderDetail = platform === "openai" ? getRenderImageDetail(info.model || DEFAULT_OPENAI_MODEL) : void 0;
|
|
950
|
+
const skapiRender = visionFile && renderPlaceholder ? {
|
|
951
|
+
_skapi_render: [
|
|
952
|
+
{
|
|
953
|
+
path: attachment.storagePath,
|
|
954
|
+
from: renderFrom,
|
|
955
|
+
count: RENDER_PAGES_PER_WINDOW,
|
|
956
|
+
placeholder: renderPlaceholder,
|
|
957
|
+
name: attachment.name,
|
|
958
|
+
mime: attachment.mime,
|
|
959
|
+
detail: renderDetail,
|
|
960
|
+
auto_continue: true,
|
|
961
|
+
continue_text: buildIndexingRenderContinueTemplate(attachment, renderPlaceholder)
|
|
962
|
+
}
|
|
963
|
+
]
|
|
964
|
+
} : {};
|
|
965
|
+
const windowedRead = !visionFile && !parsedContent && windowedIndexingEnabled() && isWindowedReadFile(attachment.name, attachment.mime);
|
|
966
|
+
const windowPlaceholder = windowedRead ? makeWindowPlaceholder(attachment.storagePath) : void 0;
|
|
967
|
+
const skapiWindow = windowedRead && windowPlaceholder ? {
|
|
968
|
+
_skapi_window: [
|
|
969
|
+
{
|
|
970
|
+
path: attachment.storagePath,
|
|
971
|
+
cursor: null,
|
|
972
|
+
placeholder: windowPlaceholder,
|
|
973
|
+
name: attachment.name,
|
|
974
|
+
mime: attachment.mime,
|
|
975
|
+
kind: "window",
|
|
976
|
+
auto_continue: true,
|
|
977
|
+
continue_text: buildIndexingWindowMessage(attachment, windowPlaceholder, true)
|
|
978
|
+
}
|
|
979
|
+
]
|
|
980
|
+
} : {};
|
|
981
|
+
const pagedRead = !visionFile && !windowedRead && (continuing || !parsedContent && isPagedReadFile(attachment.name, attachment.mime));
|
|
982
|
+
const serverExtract = !visionFile && !windowedRead && !continuing && !parsedContent && !pagedRead && isServerExtractable(attachment.name, attachment.mime);
|
|
822
983
|
const placeholder = serverExtract ? makeExtractPlaceholder(attachment.storagePath) : void 0;
|
|
823
984
|
const extractContent = serverExtract && placeholder ? [{ path: attachment.storagePath, placeholder, name: attachment.name, mime: attachment.mime }] : void 0;
|
|
824
985
|
const skapiExtract = extractContent && extractContent.length ? { _skapi_extract: extractContent } : {};
|
|
825
|
-
const userMessage = buildIndexingUserMessage(
|
|
986
|
+
const userMessage = visionFile && renderPlaceholder ? buildIndexingRenderMessage(attachment, renderPlaceholder, renderFrom) : windowedRead && windowPlaceholder ? buildIndexingWindowMessage(attachment, windowPlaceholder, false) : continuing ? buildIndexingContinueMessage(attachment) : buildIndexingUserMessage(
|
|
826
987
|
attachment,
|
|
827
988
|
parsedContent ? { inlineContent: parsedContent } : placeholder ? { inlineContentPlaceholder: placeholder } : pagedRead ? { pagedRead: true } : void 0
|
|
828
989
|
);
|
|
@@ -850,6 +1011,8 @@ async function notifyAgentSaveAttachment(info) {
|
|
|
850
1011
|
model: resolvedModel2,
|
|
851
1012
|
max_output_tokens: MAX_TOKENS,
|
|
852
1013
|
...skapiExtract,
|
|
1014
|
+
...skapiRender,
|
|
1015
|
+
...skapiWindow,
|
|
853
1016
|
input: [
|
|
854
1017
|
{ role: "system", content: systemPrompt },
|
|
855
1018
|
{
|
|
@@ -894,6 +1057,8 @@ async function notifyAgentSaveAttachment(info) {
|
|
|
894
1057
|
model: resolvedModel,
|
|
895
1058
|
max_tokens: MAX_TOKENS,
|
|
896
1059
|
...skapiExtract,
|
|
1060
|
+
...skapiRender,
|
|
1061
|
+
...skapiWindow,
|
|
897
1062
|
system: [
|
|
898
1063
|
{
|
|
899
1064
|
type: "text",
|
|
@@ -990,6 +1155,15 @@ async function listOpenAIModels(service, owner) {
|
|
|
990
1155
|
});
|
|
991
1156
|
}
|
|
992
1157
|
var BG_INDEXING_QUEUE_SUFFIX = "-bg";
|
|
1158
|
+
function isBgIndexingQueue(queueName) {
|
|
1159
|
+
if (typeof queueName !== "string" || !queueName) return false;
|
|
1160
|
+
const prefix = queueName.split("|")[0];
|
|
1161
|
+
const idx = prefix.lastIndexOf(":");
|
|
1162
|
+
const name = idx === -1 ? prefix : prefix.slice(idx + 1);
|
|
1163
|
+
return name.slice(-BG_INDEXING_QUEUE_SUFFIX.length) === BG_INDEXING_QUEUE_SUFFIX;
|
|
1164
|
+
}
|
|
1165
|
+
var INDEXING_COMPLETE_MARKER = "INDEXING_COMPLETE";
|
|
1166
|
+
var MAX_INDEXING_RESUME_PASSES = 6;
|
|
993
1167
|
async function getChatHistory(params, fetchOptions) {
|
|
994
1168
|
const url = params.platform === "claude" ? ANTHROPIC_MESSAGES_API_URL : OPENAI_RESPONSES_API_URL;
|
|
995
1169
|
const p = Object.assign(
|
|
@@ -1117,6 +1291,9 @@ function nextFrame(cb) {
|
|
|
1117
1291
|
cb(nowMs());
|
|
1118
1292
|
}, 16);
|
|
1119
1293
|
}
|
|
1294
|
+
function isPollStopped(res) {
|
|
1295
|
+
return !!res && typeof res === "object" && res.status === "stopped";
|
|
1296
|
+
}
|
|
1120
1297
|
var ChatSession = class {
|
|
1121
1298
|
constructor(host) {
|
|
1122
1299
|
this.typewriterQueue = Promise.resolve();
|
|
@@ -1140,8 +1317,83 @@ var ChatSession = class {
|
|
|
1140
1317
|
this.pendingAgentRequests = {};
|
|
1141
1318
|
this.aiChatHistoryCache = {};
|
|
1142
1319
|
this.historyItemPolls = /* @__PURE__ */ new Map();
|
|
1320
|
+
this._pauseReasons = /* @__PURE__ */ new Set();
|
|
1321
|
+
this._resuming = false;
|
|
1143
1322
|
this._lidSeq = 0;
|
|
1144
1323
|
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Register a live poll so (a) a remount dedupes against it instead of stacking a
|
|
1326
|
+
* SECOND poll on the same item, and (b) pausePolling can stop it.
|
|
1327
|
+
*
|
|
1328
|
+
* `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
|
|
1329
|
+
* poll simply cannot be stopped and is left running — see pausePolling.
|
|
1330
|
+
*/
|
|
1331
|
+
_trackPoll(id, kind, p) {
|
|
1332
|
+
var stop = p && typeof p.stop === "function" ? p.stop.bind(p) : void 0;
|
|
1333
|
+
if (!stop) {
|
|
1334
|
+
console.debug("[chat-engine] poll has no stop handle", { id, kind });
|
|
1335
|
+
}
|
|
1336
|
+
this.historyItemPolls.set(id, { kind, stop });
|
|
1337
|
+
return p;
|
|
1338
|
+
}
|
|
1339
|
+
/** True while any pause reason is active. */
|
|
1340
|
+
isPollingPaused() {
|
|
1341
|
+
return this._pauseReasons.size > 0;
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Stop BACKGROUND polling until resumePolling. Foreground polls (a reply the user is
|
|
1345
|
+
* waiting on) keep running deliberately: their results must still land in the history
|
|
1346
|
+
* cache so resumePendingRequest can render them on return, otherwise a user who sends
|
|
1347
|
+
* a message then navigates away comes back to a permanently stuck "Thinking...".
|
|
1348
|
+
*
|
|
1349
|
+
* Server-side work is untouched; this only stops asking about it. That is safe for
|
|
1350
|
+
* document indexing because the worker drives that loop itself.
|
|
1351
|
+
*/
|
|
1352
|
+
pausePolling(reason) {
|
|
1353
|
+
this._pauseReasons.add(reason || "paused");
|
|
1354
|
+
var self = this;
|
|
1355
|
+
var stopped = [];
|
|
1356
|
+
this.historyItemPolls.forEach(function(handle, id) {
|
|
1357
|
+
if (!handle || handle.kind !== "bg") return;
|
|
1358
|
+
if (typeof handle.stop !== "function") return;
|
|
1359
|
+
try {
|
|
1360
|
+
handle.stop();
|
|
1361
|
+
} catch (e) {
|
|
1362
|
+
}
|
|
1363
|
+
stopped.push(id);
|
|
1364
|
+
});
|
|
1365
|
+
stopped.forEach(function(id) {
|
|
1366
|
+
self.historyItemPolls.delete(id);
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Lift a pause reason WITHOUT running the reconcile. For a caller that is about to
|
|
1371
|
+
* reload history anyway (a view remounting), letting resumePolling also reconcile
|
|
1372
|
+
* would race that load and can double-attach.
|
|
1373
|
+
*/
|
|
1374
|
+
clearPauseReason(reason) {
|
|
1375
|
+
this._pauseReasons.delete(reason || "paused");
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Clear a pause reason and, once none remain, re-attach polling and reconcile.
|
|
1379
|
+
* Deliberately does NOT touch gateRefreshToken: bumping it would silently discard
|
|
1380
|
+
* the results of anything still in flight across the pause.
|
|
1381
|
+
*/
|
|
1382
|
+
resumePolling(reason) {
|
|
1383
|
+
this._pauseReasons.delete(reason || "paused");
|
|
1384
|
+
if (this._pauseReasons.size > 0 || this._resuming) return Promise.resolve();
|
|
1385
|
+
if (!this.host.isViewMounted || !this.host.isViewMounted()) return Promise.resolve();
|
|
1386
|
+
var self = this;
|
|
1387
|
+
this._resuming = true;
|
|
1388
|
+
return Promise.resolve().then(function() {
|
|
1389
|
+
self.drainBgTaskQueue();
|
|
1390
|
+
return self.loadHistory(false, self.state.gateRefreshToken);
|
|
1391
|
+
}).catch(function(e) {
|
|
1392
|
+
console.error("[chat-engine] resume polling failed", e);
|
|
1393
|
+
}).then(function() {
|
|
1394
|
+
self._resuming = false;
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1145
1397
|
_newLocalId() {
|
|
1146
1398
|
this._lidSeq += 1;
|
|
1147
1399
|
return "lid_" + this._lidSeq;
|
|
@@ -1155,29 +1407,86 @@ var ChatSession = class {
|
|
|
1155
1407
|
var key = this.getHistoryCacheKey();
|
|
1156
1408
|
if (!key) return;
|
|
1157
1409
|
this.aiChatHistoryCache[key] = {
|
|
1158
|
-
messages: this.state.messages.
|
|
1410
|
+
messages: this.state.messages.filter(function(m) {
|
|
1411
|
+
return m._ownerKey === void 0 || m._ownerKey === key;
|
|
1412
|
+
}),
|
|
1159
1413
|
endOfList: this.state.historyEndOfList,
|
|
1160
1414
|
startKeyHistory: this.state.historyStartKeyHistory.slice()
|
|
1161
1415
|
};
|
|
1162
1416
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1417
|
+
/**
|
|
1418
|
+
* Land a resolved reply in the history cache of a chat that is NOT currently
|
|
1419
|
+
* visible, without touching state.messages. Mirrors the cache-only path in
|
|
1420
|
+
* dispatchAgentRequest: REPLACE the trailing pending "Thinking..." bubble
|
|
1421
|
+
* (append only when there is none), and settle the matching pending user
|
|
1422
|
+
* bubble, so the cached copy never keeps a stuck "Thinking..." that a later
|
|
1423
|
+
* cache-first load would re-render forever.
|
|
1424
|
+
*/
|
|
1425
|
+
_applyReplyToCache(key, reply, serverId) {
|
|
1426
|
+
if (!key) return;
|
|
1427
|
+
var existing = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1428
|
+
var msgs = existing.messages.slice();
|
|
1429
|
+
var thIdx = -1;
|
|
1430
|
+
for (var i = msgs.length - 1; i >= 0; i--) {
|
|
1431
|
+
var m = msgs[i];
|
|
1432
|
+
if (!m || !m.isPending || m.role !== "assistant" || m.isBackgroundTask) continue;
|
|
1433
|
+
if (serverId && m._serverItemId && m._serverItemId !== serverId) continue;
|
|
1434
|
+
thIdx = i;
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
if (thIdx !== -1) {
|
|
1438
|
+
if (reply._serverItemId === void 0 && msgs[thIdx]._serverItemId !== void 0) reply._serverItemId = msgs[thIdx]._serverItemId;
|
|
1439
|
+
msgs[thIdx] = reply;
|
|
1440
|
+
} else {
|
|
1441
|
+
msgs.push(reply);
|
|
1442
|
+
}
|
|
1443
|
+
for (var j = 0; j < msgs.length; j++) {
|
|
1444
|
+
var u = msgs[j];
|
|
1445
|
+
if (!u || u.role !== "user" || u.isBackgroundTask) continue;
|
|
1446
|
+
if (!(u.isPendingQueued || u.isPendingInProcess || u.isSendingToServer)) continue;
|
|
1447
|
+
if (serverId && u._serverItemId && u._serverItemId !== serverId) continue;
|
|
1448
|
+
var settled = { role: "user", content: u.content };
|
|
1449
|
+
if (u._serverItemId !== void 0) settled._serverItemId = u._serverItemId;
|
|
1450
|
+
if (u._ownerKey !== void 0) settled._ownerKey = u._ownerKey;
|
|
1451
|
+
msgs[j] = settled;
|
|
1452
|
+
break;
|
|
1453
|
+
}
|
|
1454
|
+
this.aiChatHistoryCache[key] = {
|
|
1455
|
+
messages: msgs,
|
|
1456
|
+
endOfList: existing.endOfList,
|
|
1457
|
+
startKeyHistory: existing.startKeyHistory
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* serviceId/owner are passed explicitly by every caller: a request can be
|
|
1462
|
+
* dispatched after the user moved to another project, and re-reading the live
|
|
1463
|
+
* identity here would silently send the turn to THAT project instead of the
|
|
1464
|
+
* one it was composed for. Falls back to the live read only when a caller
|
|
1465
|
+
* omits them.
|
|
1466
|
+
*/
|
|
1467
|
+
_callProviderFor(platform, prompt, messages, system, model, userId, extractContent, fileUrls, serviceId, owner) {
|
|
1468
|
+
if (serviceId === void 0 || owner === void 0) {
|
|
1469
|
+
var id = this.host.getIdentity();
|
|
1470
|
+
if (serviceId === void 0) serviceId = id.serviceId;
|
|
1471
|
+
if (owner === void 0) owner = id.owner;
|
|
1472
|
+
}
|
|
1473
|
+
return platform === "openai" ? callOpenAIWithPublicMcp(prompt, serviceId, owner, messages, system, model, userId, extractContent, fileUrls) : callClaudeWithPublicMcp(prompt, serviceId, owner, messages, system, model, userId, extractContent, fileUrls);
|
|
1166
1474
|
}
|
|
1167
1475
|
dispatchAgentRequest(params) {
|
|
1168
1476
|
var self = this;
|
|
1169
1477
|
var dispatchItemId;
|
|
1170
1478
|
var sendAndPoll = function() {
|
|
1171
1479
|
return Promise.resolve(
|
|
1172
|
-
self._callProviderFor(params.aiPlatform, params.text, params.boundedMessages, params.systemPrompt, params.aiModel, params.userId, params.extractContent, params.fileUrls)
|
|
1480
|
+
self._callProviderFor(params.aiPlatform, params.text, params.boundedMessages, params.systemPrompt, params.aiModel, params.userId, params.extractContent, params.fileUrls, params.serviceId, params.owner)
|
|
1173
1481
|
).then(function(initial) {
|
|
1174
1482
|
if (initial && initial.poll && (initial.status === "pending" || initial.status === "running")) {
|
|
1175
1483
|
if (initial.id) {
|
|
1176
1484
|
if (dispatchItemId && dispatchItemId !== initial.id) self.historyItemPolls.delete(dispatchItemId);
|
|
1177
1485
|
dispatchItemId = initial.id;
|
|
1178
|
-
self.historyItemPolls.set(initial.id, true);
|
|
1179
1486
|
}
|
|
1180
|
-
|
|
1487
|
+
var dp = initial.poll({ latency: POLL_INTERVAL });
|
|
1488
|
+
if (initial.id) self._trackPoll(initial.id, "fg", dp);
|
|
1489
|
+
return dp;
|
|
1181
1490
|
}
|
|
1182
1491
|
return initial;
|
|
1183
1492
|
});
|
|
@@ -1230,20 +1539,57 @@ var ChatSession = class {
|
|
|
1230
1539
|
// composed = clean display text; composedForLlm carries office-extraction
|
|
1231
1540
|
// placeholders for the provider only. useBgQueue routes a post-attachment turn
|
|
1232
1541
|
// onto the "-bg" queue so it runs after indexing.
|
|
1233
|
-
dispatchComposedMessage(composed, useBgQueue, composedForLlm, extractContent, fileUrls) {
|
|
1542
|
+
dispatchComposedMessage(composed, useBgQueue, composedForLlm, extractContent, fileUrls, pinned) {
|
|
1234
1543
|
var self = this;
|
|
1235
1544
|
if (!composed) return;
|
|
1236
|
-
var id = this.host.getIdentity();
|
|
1545
|
+
var id = pinned ? pinned.identity : this.host.getIdentity();
|
|
1237
1546
|
if (id.platform === "none") return;
|
|
1238
1547
|
var llmComposed = composedForLlm || composed;
|
|
1239
|
-
var
|
|
1548
|
+
var key = !id.serviceId ? "" : id.serviceId + "#" + id.platform;
|
|
1549
|
+
var offChat = !!key && key !== this.getHistoryCacheKey();
|
|
1550
|
+
var isQueuedSend = !offChat && (useBgQueue || this.state.sending || this.state.messages.some(function(m) {
|
|
1240
1551
|
return (m.isPending || m.isPendingQueued) && !m.isBackgroundTask && !m._useBgQueue;
|
|
1241
|
-
});
|
|
1552
|
+
}));
|
|
1242
1553
|
var aiPlatform = id.platform;
|
|
1243
1554
|
var aiModel = id.model || void 0;
|
|
1244
|
-
var systemPrompt = this.host.buildSystemPrompt();
|
|
1555
|
+
var systemPrompt = pinned ? pinned.systemPrompt : this.host.buildSystemPrompt();
|
|
1245
1556
|
var userId = id.userId || id.serviceId;
|
|
1246
1557
|
var chatQueue = useBgQueue ? userId + BG_INDEXING_QUEUE_SUFFIX : userId;
|
|
1558
|
+
if (offChat) {
|
|
1559
|
+
var offHistory = (this.aiChatHistoryCache[key] ? this.aiChatHistoryCache[key].messages : []).filter(function(m) {
|
|
1560
|
+
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask;
|
|
1561
|
+
});
|
|
1562
|
+
var offBounded = buildBoundedChatMessages({
|
|
1563
|
+
platform: aiPlatform,
|
|
1564
|
+
model: aiModel,
|
|
1565
|
+
systemPrompt,
|
|
1566
|
+
serviceId: id.serviceId,
|
|
1567
|
+
history: offHistory.concat([{ role: "user", content: llmComposed }])
|
|
1568
|
+
});
|
|
1569
|
+
var offExisting = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1570
|
+
this.aiChatHistoryCache[key] = {
|
|
1571
|
+
messages: offExisting.messages.concat([
|
|
1572
|
+
{ role: "user", content: composed, _ownerKey: key },
|
|
1573
|
+
{ role: "assistant", content: "", isPending: true, isPendingInProcess: true, _ownerKey: key }
|
|
1574
|
+
]),
|
|
1575
|
+
endOfList: offExisting.endOfList,
|
|
1576
|
+
startKeyHistory: offExisting.startKeyHistory
|
|
1577
|
+
};
|
|
1578
|
+
this.dispatchAgentRequest({
|
|
1579
|
+
key,
|
|
1580
|
+
serviceId: id.serviceId,
|
|
1581
|
+
owner: id.owner,
|
|
1582
|
+
aiPlatform,
|
|
1583
|
+
aiModel,
|
|
1584
|
+
systemPrompt,
|
|
1585
|
+
text: composed,
|
|
1586
|
+
boundedMessages: offBounded.messages,
|
|
1587
|
+
userId: chatQueue,
|
|
1588
|
+
extractContent,
|
|
1589
|
+
fileUrls
|
|
1590
|
+
});
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1247
1593
|
if (isQueuedSend) {
|
|
1248
1594
|
var resolvedHistory = this.state.messages.filter(function(m) {
|
|
1249
1595
|
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask;
|
|
@@ -1256,15 +1602,16 @@ var ChatSession = class {
|
|
|
1256
1602
|
history: resolvedHistory.concat([{ role: "user", content: llmComposed }])
|
|
1257
1603
|
});
|
|
1258
1604
|
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true };
|
|
1605
|
+
if (key) queuedBubble._ownerKey = key;
|
|
1259
1606
|
if (useBgQueue) queuedBubble._useBgQueue = true;
|
|
1260
1607
|
this.state.messages.push(queuedBubble);
|
|
1261
1608
|
this.host.notify();
|
|
1262
1609
|
this.updateHistoryCache();
|
|
1263
1610
|
this.host.scrollToBottom(true);
|
|
1264
|
-
var capturedComposed = composed, capturedPlatform = aiPlatform;
|
|
1265
|
-
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls)).then(function(result) {
|
|
1266
|
-
var sendingIdx = self.state.messages.findIndex(function(m) {
|
|
1267
|
-
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user";
|
|
1611
|
+
var capturedComposed = composed, capturedPlatform = aiPlatform, capturedKey = key;
|
|
1612
|
+
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls, id.serviceId, id.owner)).then(function(result) {
|
|
1613
|
+
var sendingIdx = self.getHistoryCacheKey() !== capturedKey ? -1 : self.state.messages.findIndex(function(m) {
|
|
1614
|
+
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user" && (m._ownerKey === void 0 || m._ownerKey === capturedKey);
|
|
1268
1615
|
});
|
|
1269
1616
|
var serverId = result && typeof result.id === "string" ? result.id : void 0;
|
|
1270
1617
|
if (sendingIdx >= 0) {
|
|
@@ -1274,26 +1621,27 @@ var ChatSession = class {
|
|
|
1274
1621
|
self.host.notify();
|
|
1275
1622
|
}
|
|
1276
1623
|
if (result && result.poll && (result.status === "pending" || result.status === "running")) {
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1624
|
+
var qp = result.poll({ latency: POLL_INTERVAL });
|
|
1625
|
+
if (serverId) self._trackPoll(serverId, "fg", qp);
|
|
1626
|
+
return qp.then(function(res) {
|
|
1627
|
+
if (isPollStopped(res)) return;
|
|
1628
|
+
return self.onQueuedSendResponse(capturedComposed, res, capturedPlatform, serverId, capturedKey);
|
|
1280
1629
|
}).catch(function(err) {
|
|
1281
|
-
return self.onQueuedSendError(capturedComposed, err, serverId);
|
|
1630
|
+
return self.onQueuedSendError(capturedComposed, err, serverId, capturedKey);
|
|
1282
1631
|
});
|
|
1283
1632
|
}
|
|
1284
|
-
return self.onQueuedSendResponse(capturedComposed, result, capturedPlatform, serverId);
|
|
1633
|
+
return self.onQueuedSendResponse(capturedComposed, result, capturedPlatform, serverId, capturedKey);
|
|
1285
1634
|
}).catch(function(err) {
|
|
1286
|
-
return self.onQueuedSendError(capturedComposed, err, void 0);
|
|
1635
|
+
return self.onQueuedSendError(capturedComposed, err, void 0, capturedKey);
|
|
1287
1636
|
});
|
|
1288
1637
|
return;
|
|
1289
1638
|
}
|
|
1290
|
-
this.state.messages.push({ role: "user", content: composed });
|
|
1291
|
-
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true });
|
|
1639
|
+
this.state.messages.push({ role: "user", content: composed, ...key ? { _ownerKey: key } : {} });
|
|
1640
|
+
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} });
|
|
1292
1641
|
this.host.notify();
|
|
1293
1642
|
this.updateHistoryCache();
|
|
1294
1643
|
this.state.sending = true;
|
|
1295
1644
|
this.host.scrollToBottom(true);
|
|
1296
|
-
var key = this.getHistoryCacheKey();
|
|
1297
1645
|
var historyForLlm = this.state.messages.filter(function(m) {
|
|
1298
1646
|
return !m.isCancelled && !m.isBackgroundTask;
|
|
1299
1647
|
});
|
|
@@ -1327,8 +1675,8 @@ var ChatSession = class {
|
|
|
1327
1675
|
});
|
|
1328
1676
|
Promise.resolve(run).catch(function() {
|
|
1329
1677
|
}).then(function() {
|
|
1330
|
-
if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
|
|
1331
1678
|
self.state.sending = false;
|
|
1679
|
+
if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
|
|
1332
1680
|
return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
|
|
1333
1681
|
self.host.scrollToBottom(true);
|
|
1334
1682
|
});
|
|
@@ -1345,9 +1693,11 @@ var ChatSession = class {
|
|
|
1345
1693
|
var existing = this.state.messages[nextIdx];
|
|
1346
1694
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true, isBackgroundTask: true };
|
|
1347
1695
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1696
|
+
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1348
1697
|
this.state.messages[nextIdx] = promoted;
|
|
1349
1698
|
var placeholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, isBackgroundTask: true };
|
|
1350
1699
|
if (existing._serverItemId !== void 0) placeholder._serverItemId = existing._serverItemId;
|
|
1700
|
+
if (existing._ownerKey !== void 0) placeholder._ownerKey = existing._ownerKey;
|
|
1351
1701
|
this.state.messages.splice(nextIdx + 1, 0, placeholder);
|
|
1352
1702
|
this.host.notify();
|
|
1353
1703
|
}
|
|
@@ -1363,14 +1713,20 @@ var ChatSession = class {
|
|
|
1363
1713
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true };
|
|
1364
1714
|
if (existing.isBackgroundTask) promoted.isBackgroundTask = true;
|
|
1365
1715
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1716
|
+
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1366
1717
|
if (existing.isSendingToServer) promoted.isSendingToServer = true;
|
|
1367
1718
|
this.state.messages[nextIdx] = promoted;
|
|
1368
1719
|
var placeholder = { role: "assistant", content: "", isPending: true };
|
|
1369
1720
|
if (existing._serverItemId !== void 0) placeholder._serverItemId = existing._serverItemId;
|
|
1721
|
+
if (existing._ownerKey !== void 0) placeholder._ownerKey = existing._ownerKey;
|
|
1370
1722
|
this.state.messages.splice(nextIdx + 1, 0, placeholder);
|
|
1371
1723
|
this.host.notify();
|
|
1372
1724
|
}
|
|
1373
1725
|
resolveQueuedUserBubble(serverId) {
|
|
1726
|
+
var liveKey = this.getHistoryCacheKey();
|
|
1727
|
+
var isLocal = function(m) {
|
|
1728
|
+
return m._ownerKey === void 0 || m._ownerKey === liveKey;
|
|
1729
|
+
};
|
|
1374
1730
|
var userIdx = -1;
|
|
1375
1731
|
if (serverId) {
|
|
1376
1732
|
userIdx = this.state.messages.findIndex(function(m) {
|
|
@@ -1379,19 +1735,19 @@ var ChatSession = class {
|
|
|
1379
1735
|
}
|
|
1380
1736
|
if (userIdx === -1) {
|
|
1381
1737
|
userIdx = this.state.messages.findIndex(function(m) {
|
|
1382
|
-
return m.isPendingInProcess && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1738
|
+
return m.isPendingInProcess && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue && isLocal(m);
|
|
1383
1739
|
});
|
|
1384
1740
|
}
|
|
1385
1741
|
if (userIdx === -1) {
|
|
1386
1742
|
userIdx = this.state.messages.findIndex(function(m) {
|
|
1387
|
-
return m.isPendingQueued && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1743
|
+
return m.isPendingQueued && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue && isLocal(m);
|
|
1388
1744
|
});
|
|
1389
1745
|
}
|
|
1390
1746
|
if (serverId && this.cancelledServerIds.has(serverId)) {
|
|
1391
1747
|
this.cancelledServerIds.delete(serverId);
|
|
1392
1748
|
if (userIdx >= 0) {
|
|
1393
1749
|
var ex = this.state.messages[userIdx];
|
|
1394
|
-
this.state.messages[userIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId };
|
|
1750
|
+
this.state.messages[userIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId, ...ex._ownerKey !== void 0 ? { _ownerKey: ex._ownerKey } : {} };
|
|
1395
1751
|
var thIdx = this.state.messages.findIndex(function(m, i) {
|
|
1396
1752
|
return i > userIdx && m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1397
1753
|
});
|
|
@@ -1404,6 +1760,7 @@ var ChatSession = class {
|
|
|
1404
1760
|
var exist = this.state.messages[userIdx];
|
|
1405
1761
|
var repl = { role: "user", content: exist.content };
|
|
1406
1762
|
if (exist._serverItemId !== void 0) repl._serverItemId = exist._serverItemId;
|
|
1763
|
+
if (exist._ownerKey !== void 0) repl._ownerKey = exist._ownerKey;
|
|
1407
1764
|
this.state.messages[userIdx] = repl;
|
|
1408
1765
|
}
|
|
1409
1766
|
var thinkingIdx = userIdx >= 0 ? this.state.messages.findIndex(function(m, i) {
|
|
@@ -1416,8 +1773,14 @@ var ChatSession = class {
|
|
|
1416
1773
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
1417
1774
|
else this.state.messages.push(msg);
|
|
1418
1775
|
}
|
|
1419
|
-
onQueuedSendResponse(_composed, response, platform, serverId) {
|
|
1776
|
+
onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
|
|
1420
1777
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
1778
|
+
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
1779
|
+
var offReply = isErrorResponseBody(response) ? { role: "assistant", content: getErrorMessage(response), isError: true } : { role: "assistant", content: ((platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "").trim() || "No text response received from AI provider." };
|
|
1780
|
+
this._applyReplyToCache(ownerKey, offReply, serverId);
|
|
1781
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1421
1784
|
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
1422
1785
|
if (targetIdx === void 0) {
|
|
1423
1786
|
this.host.notify();
|
|
@@ -1451,8 +1814,14 @@ var ChatSession = class {
|
|
|
1451
1814
|
this.host.notify();
|
|
1452
1815
|
this.host.scrollToBottom(true);
|
|
1453
1816
|
}
|
|
1454
|
-
onQueuedSendError(_composed, err, serverId) {
|
|
1817
|
+
onQueuedSendError(_composed, err, serverId, ownerKey) {
|
|
1455
1818
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
1819
|
+
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
1820
|
+
var isGone = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
1821
|
+
this._applyReplyToCache(ownerKey, isGone ? { role: "assistant", content: "Request was cancelled.", isError: true } : { role: "assistant", content: getErrorMessage(err), isError: true }, serverId);
|
|
1822
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1456
1825
|
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
1457
1826
|
if (isNotExists) {
|
|
1458
1827
|
var userIdx = serverId ? this.state.messages.findIndex(function(m) {
|
|
@@ -1798,6 +2167,7 @@ var ChatSession = class {
|
|
|
1798
2167
|
this.historyItemPolls.delete(itemId);
|
|
1799
2168
|
var isErr = isErrorResponseBody(response);
|
|
1800
2169
|
var answer = isErr ? getErrorMessage(response) : ((platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "").trim();
|
|
2170
|
+
if (!isErr && answer) answer = answer.split(INDEXING_COMPLETE_MARKER).join("").trim();
|
|
1801
2171
|
var idx = this.state.messages.findIndex(function(m) {
|
|
1802
2172
|
return m.isPending && m._serverItemId === itemId;
|
|
1803
2173
|
});
|
|
@@ -1855,24 +2225,33 @@ var ChatSession = class {
|
|
|
1855
2225
|
}
|
|
1856
2226
|
this.bgTaskQueue.forEach(function(entry) {
|
|
1857
2227
|
if (entry.serviceId !== svcId || entry.platform !== plat) return;
|
|
1858
|
-
if (presentIds[entry.id])
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2228
|
+
if (!presentIds[entry.id]) {
|
|
2229
|
+
var isRunning = entry.status === "running";
|
|
2230
|
+
var userBubble = { role: "user", content: self.host.formatIndexingLabel(entry.filename, entry.mime, entry.size, entry.storagePath, entry.isReindex), isBackgroundTask: true, _serverItemId: entry.id };
|
|
2231
|
+
if (isRunning) userBubble.isPendingInProcess = true;
|
|
2232
|
+
else userBubble.isPendingQueued = true;
|
|
2233
|
+
self.state.messages.push(userBubble);
|
|
2234
|
+
if (isRunning) {
|
|
2235
|
+
self.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, isBackgroundTask: true, _serverItemId: entry.id });
|
|
2236
|
+
}
|
|
2237
|
+
presentIds[entry.id] = true;
|
|
2238
|
+
self.host.notify();
|
|
2239
|
+
self.updateHistoryCache();
|
|
2240
|
+
self.host.scrollToBottom(false);
|
|
1866
2241
|
}
|
|
1867
|
-
|
|
1868
|
-
self.host.notify();
|
|
1869
|
-
self.updateHistoryCache();
|
|
1870
|
-
self.host.scrollToBottom(false);
|
|
1871
|
-
if (!self.historyItemPolls.has(entry.id) && typeof entry.poll === "function") {
|
|
1872
|
-
self.historyItemPolls.set(entry.id, true);
|
|
2242
|
+
if (!self.isPollingPaused() && !self.historyItemPolls.has(entry.id) && typeof entry.poll === "function") {
|
|
1873
2243
|
var capturedId = entry.id, capturedPlat = plat;
|
|
1874
|
-
|
|
2244
|
+
var capturedEntry = entry;
|
|
2245
|
+
var wasStopped = false;
|
|
2246
|
+
var bp = entry.poll({ latency: POLL_INTERVAL });
|
|
2247
|
+
self._trackPoll(entry.id, "bg", bp);
|
|
2248
|
+
bp.then(function(response) {
|
|
2249
|
+
if (isPollStopped(response)) {
|
|
2250
|
+
wasStopped = true;
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
1875
2253
|
self.handleHistoryItemResolution(capturedId, response, capturedPlat);
|
|
2254
|
+
self.maybeResumeIndexing(capturedEntry, response, capturedPlat);
|
|
1876
2255
|
}).catch(function(err) {
|
|
1877
2256
|
self.historyItemPolls.delete(capturedId);
|
|
1878
2257
|
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
@@ -1886,6 +2265,7 @@ var ChatSession = class {
|
|
|
1886
2265
|
self.updateHistoryCache();
|
|
1887
2266
|
}
|
|
1888
2267
|
}).then(function() {
|
|
2268
|
+
if (wasStopped) return;
|
|
1889
2269
|
var qi = self.bgTaskQueue.findIndex(function(q) {
|
|
1890
2270
|
return q.id === capturedId;
|
|
1891
2271
|
});
|
|
@@ -1895,6 +2275,69 @@ var ChatSession = class {
|
|
|
1895
2275
|
});
|
|
1896
2276
|
this.promoteNextBgQueuedToRunning();
|
|
1897
2277
|
}
|
|
2278
|
+
// Resume-across-passes: if a background INDEXING task for a paged file (spreadsheet or
|
|
2279
|
+
// text) finished WITHOUT the completion marker, the agent ran out of room before reading
|
|
2280
|
+
// the whole file - dispatch a CONTINUE pass (up to a cap) that resumes from where the
|
|
2281
|
+
// already-saved records leave off. Additive + guarded so it never loops forever and
|
|
2282
|
+
// never breaks the resolution path.
|
|
2283
|
+
//
|
|
2284
|
+
// VISION files (PDFs rendered to page images) are NOT resumed here: the proxy worker
|
|
2285
|
+
// advances their page window itself, off the renderer's true page count. Driving them
|
|
2286
|
+
// from the browser is what used to lose pages on long documents - the chain lived in tab
|
|
2287
|
+
// memory (a reload or a closed tab ended it), and it stopped whenever the model claimed
|
|
2288
|
+
// completion, which on an 88-page file happened at page 15. Continuing to dispatch here
|
|
2289
|
+
// as well would now double-index every window.
|
|
2290
|
+
maybeResumeIndexing(entry, response, platform) {
|
|
2291
|
+
var self = this;
|
|
2292
|
+
try {
|
|
2293
|
+
if (!entry || !entry.storagePath) return;
|
|
2294
|
+
if (!isPagedReadFile(entry.filename, entry.mime)) return;
|
|
2295
|
+
if (isImageVisionFile(entry.filename, entry.mime)) return;
|
|
2296
|
+
if (isErrorResponseBody(response)) return;
|
|
2297
|
+
var answer = (platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "";
|
|
2298
|
+
if (answer.indexOf(INDEXING_COMPLETE_MARKER) !== -1) return;
|
|
2299
|
+
var pass = (entry.resumePass || 0) + 1;
|
|
2300
|
+
if (pass > MAX_INDEXING_RESUME_PASSES) return;
|
|
2301
|
+
var id = this.host.getIdentity();
|
|
2302
|
+
if (!id || id.platform === "none" || id.serviceId !== entry.serviceId) return;
|
|
2303
|
+
notifyAgentContinueIndexing({
|
|
2304
|
+
platform: id.platform,
|
|
2305
|
+
model: id.model,
|
|
2306
|
+
service: id.serviceId,
|
|
2307
|
+
owner: id.owner,
|
|
2308
|
+
userId: id.userId || id.serviceId,
|
|
2309
|
+
serviceName: id.serviceName,
|
|
2310
|
+
serviceDescription: id.serviceDescription,
|
|
2311
|
+
attachment: {
|
|
2312
|
+
name: entry.filename,
|
|
2313
|
+
storagePath: entry.storagePath,
|
|
2314
|
+
mime: entry.mime,
|
|
2315
|
+
size: entry.size,
|
|
2316
|
+
url: ""
|
|
2317
|
+
}
|
|
2318
|
+
}).then(function(ack) {
|
|
2319
|
+
if (ack && typeof ack.id === "string") {
|
|
2320
|
+
self.bgTaskQueue.push({
|
|
2321
|
+
serviceId: id.serviceId,
|
|
2322
|
+
platform: id.platform,
|
|
2323
|
+
id: ack.id,
|
|
2324
|
+
filename: entry.filename,
|
|
2325
|
+
storagePath: entry.storagePath,
|
|
2326
|
+
isReindex: entry.isReindex,
|
|
2327
|
+
mime: entry.mime,
|
|
2328
|
+
size: entry.size,
|
|
2329
|
+
status: ack.status === "running" ? "running" : "pending",
|
|
2330
|
+
poll: ack.poll,
|
|
2331
|
+
resumePass: pass
|
|
2332
|
+
});
|
|
2333
|
+
self.drainBgTaskQueue();
|
|
2334
|
+
}
|
|
2335
|
+
}, function(e) {
|
|
2336
|
+
console.error("[chat-engine] resume-indexing dispatch failed", e);
|
|
2337
|
+
});
|
|
2338
|
+
} catch (e) {
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
1898
2341
|
// --- history fetch + pagination --------------------------------------
|
|
1899
2342
|
// Initial load (fetchMore=false) replaces the list (with in-flight rescue +
|
|
1900
2343
|
// cancelled-merge) and attaches polls to running/pending items; pagination
|
|
@@ -1904,6 +2347,7 @@ var ChatSession = class {
|
|
|
1904
2347
|
loadHistory(fetchMore, token) {
|
|
1905
2348
|
var self = this;
|
|
1906
2349
|
var id = this.host.getIdentity();
|
|
2350
|
+
var loadKey = !id.serviceId || id.platform === "none" ? "" : id.serviceId + "#" + id.platform;
|
|
1907
2351
|
if (token === void 0) token = this.state.gateRefreshToken;
|
|
1908
2352
|
if (this.state.loadingHistory && this.state.historyRequestToken === token || id.platform === "none" || !id.serviceId) {
|
|
1909
2353
|
return Promise.resolve();
|
|
@@ -1926,9 +2370,9 @@ var ChatSession = class {
|
|
|
1926
2370
|
if (token !== self.state.gateRefreshToken) return;
|
|
1927
2371
|
var chatList = history && Array.isArray(history.list) ? history.list : [];
|
|
1928
2372
|
chatList.forEach(function(item) {
|
|
1929
|
-
if (
|
|
2373
|
+
if (isBgIndexingQueue(item.queue_name)) {
|
|
1930
2374
|
var userText = extractLastUserTextFromRequest(item.request_body);
|
|
1931
|
-
if (typeof userText === "string" && userText.indexOf("A new file has just been uploaded") === 0) item._isBgTask = true;
|
|
2375
|
+
if (typeof userText === "string" && (userText.indexOf("A new file has just been uploaded") === 0 || userText.indexOf("CONTINUE indexing") === 0)) item._isBgTask = true;
|
|
1932
2376
|
else item._isOnBgQueue = true;
|
|
1933
2377
|
}
|
|
1934
2378
|
});
|
|
@@ -1960,6 +2404,7 @@ var ChatSession = class {
|
|
|
1960
2404
|
for (var ri = 0; ri < self.state.messages.length; ri++) {
|
|
1961
2405
|
var mm = self.state.messages[ri];
|
|
1962
2406
|
if (mm.isBackgroundTask) continue;
|
|
2407
|
+
if (mm._ownerKey !== void 0 && mm._ownerKey !== loadKey) continue;
|
|
1963
2408
|
if (mm._serverItemId && serverIds[mm._serverItemId]) continue;
|
|
1964
2409
|
if (!mm._serverItemId) {
|
|
1965
2410
|
if (mappedHasPendingAssistant) continue;
|
|
@@ -2004,11 +2449,12 @@ var ChatSession = class {
|
|
|
2004
2449
|
if (!item.poll || !item.id) return;
|
|
2005
2450
|
if (self.historyItemPolls.has(item.id)) return;
|
|
2006
2451
|
if (self.pendingAgentRequests[self.getHistoryCacheKey()] && !item._isBgTask && !item._isOnBgQueue) return;
|
|
2007
|
-
|
|
2452
|
+
if ((item._isBgTask || item._isOnBgQueue) && self.isPollingPaused()) return;
|
|
2008
2453
|
var capturedId = item.id;
|
|
2009
2454
|
var pp = item.poll({
|
|
2010
2455
|
latency: POLL_INTERVAL,
|
|
2011
2456
|
onResponse: function(response) {
|
|
2457
|
+
if (isPollStopped(response)) return;
|
|
2012
2458
|
self.handleHistoryItemResolution(capturedId, response, platform);
|
|
2013
2459
|
},
|
|
2014
2460
|
onError: function(err) {
|
|
@@ -2044,6 +2490,7 @@ var ChatSession = class {
|
|
|
2044
2490
|
}
|
|
2045
2491
|
}
|
|
2046
2492
|
});
|
|
2493
|
+
self._trackPoll(capturedId, item._isBgTask || item._isOnBgQueue ? "bg" : "fg", pp);
|
|
2047
2494
|
if (pp && pp.catch) pp.catch(function() {
|
|
2048
2495
|
});
|
|
2049
2496
|
});
|
|
@@ -2256,6 +2703,6 @@ var ChatSession = class {
|
|
|
2256
2703
|
}
|
|
2257
2704
|
};
|
|
2258
2705
|
|
|
2259
|
-
export { BG_INDEXING_QUEUE_SUFFIX, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, HISTORY_TOKEN_BUDGET, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, 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 };
|
|
2706
|
+
export { BG_INDEXING_QUEUE_SUFFIX, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, HISTORY_TOKEN_BUDGET, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, clearAttachmentParsers, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, parseAttachmentContent, registerAttachmentParser, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
|
|
2260
2707
|
//# sourceMappingURL=engine.mjs.map
|
|
2261
2708
|
//# sourceMappingURL=engine.mjs.map
|