bunnyquery 1.5.6 → 1.5.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunnyquery",
3
- "version": "1.5.6",
3
+ "version": "1.5.7",
4
4
  "description": "Embeddable BunnyQuery AI chat widget + its framework-agnostic chat engine",
5
5
  "main": "bunnyquery.js",
6
6
  "exports": {
@@ -101,6 +101,30 @@ export function isServerExtractable(name?: string, mime?: string): boolean {
101
101
  /** @deprecated renamed to {@link isServerExtractable} (now also covers text files). */
102
102
  export const isOfficeFile = isServerExtractable;
103
103
 
104
+ // Extensions that are best read WINDOW-BY-WINDOW via the readFileContent tool instead of
105
+ // inlined once: spreadsheets (grid rows + embedded photos, and can be huge) and PDFs
106
+ // (scanned page images). For these the indexing agent is told to page through the whole
107
+ // file with readFileContent rather than reading a capped inline dump or web_fetching a URL.
108
+ const PAGED_READ_EXTENSIONS = new Set(['xls', 'xlsx', 'xlsm', 'ods', 'pdf']);
109
+
110
+ /**
111
+ * True when a file should be indexed by PAGING through readFileContent (spreadsheets and
112
+ * PDFs), rather than inline extraction or a web_fetch URL. This is what lets a huge sheet
113
+ * be read row-window by row-window and a scanned PDF be read page-image by page-image,
114
+ * with embedded photos delivered to the vision model.
115
+ */
116
+ export function isPagedReadFile(name?: string, mime?: string): boolean {
117
+ const ext = (name || '').split('.').pop()?.toLowerCase() || '';
118
+ if (PAGED_READ_EXTENSIONS.has(ext)) return true;
119
+ const m = (mime || '').toLowerCase();
120
+ return (
121
+ m === 'application/pdf' ||
122
+ m === 'application/vnd.ms-excel' ||
123
+ m.includes('spreadsheetml') ||
124
+ m.includes('opendocument.spreadsheet')
125
+ );
126
+ }
127
+
104
128
  // Monotonic counter so placeholders are unique even for same-named files in one
105
129
  // request. Token shape must match the worker's _EXTRACT_PLACEHOLDER_RE:
106
130
  // {{SKAPI_FILE_CONTENT::<id>}}.
@@ -36,6 +36,12 @@ export type BuildIndexingUserMessageOptions = {
36
36
  * web_fetch for this file. Takes precedence over `inlineContentPlaceholder`.
37
37
  */
38
38
  inlineContent?: string;
39
+ /**
40
+ * Spreadsheet or PDF: read by PAGING through the readFileContent tool (grid rows +
41
+ * embedded photos / rendered scanned pages), not inline and not by web_fetch. The
42
+ * message instructs the agent to page through EVERY window and datafy each.
43
+ */
44
+ pagedRead?: boolean;
39
45
  };
40
46
 
41
47
  export function buildIndexingUserMessage(
@@ -78,5 +84,19 @@ export function buildIndexingUserMessage(
78
84
  );
79
85
  }
80
86
 
87
+ if (options?.pagedRead) {
88
+ // Spreadsheet / PDF: force the paging path. The agent MUST read this with the
89
+ // readFileContent tool (which returns the file window by window, with grid rows,
90
+ // embedded photos, and rendered scanned pages), NOT by fetching the URL.
91
+ return (
92
+ head +
93
+ `\nRead this file with the readFileContent tool, using the storage path above - do NOT fetch a URL and do NOT rely on a single sample. ` +
94
+ `readFileContent returns the file ONE WINDOW at a time: spreadsheets as coordinate-tagged grid rows (e.g. 'R4 A:E&I NUMBER | B:E1007'), scanned/large PDFs as rendered PAGE IMAGES, and windows may include embedded photos - LOOK at any images and datafy what they show. ` +
95
+ `Page through EVERY window: for each window SAVE records for its rows/items/pages (postRecords, one record per row/item), 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. ` +
96
+ `Do NOT stop after the first window and do NOT just write a summary. Use the storage path above for the "src::" unique_id.` +
97
+ (attachment.url ? `\n(A temporary URL is provided ONLY as a fallback if readFileContent fails: ${attachment.url})` : '')
98
+ );
99
+ }
100
+
81
101
  return head + `- temporary URL (fetch this to read the file contents): ${attachment.url}`;
82
102
  }
@@ -11,7 +11,7 @@
11
11
  * only the BgTaskEntry TYPE lives here)
12
12
  */
13
13
  import { buildIndexingSystemPrompt, buildIndexingUserMessage } from './prompts';
14
- import { isServerExtractable, makeExtractPlaceholder, type ExtractDirective, type FileUrlDirective } from './office';
14
+ import { isServerExtractable, isPagedReadFile, makeExtractPlaceholder, type ExtractDirective, type FileUrlDirective } from './office';
15
15
  import { chatEngineConfig, pollOpt } from './config';
16
16
 
17
17
  export const ANTHROPIC_MESSAGES_API_URL = 'https://api.anthropic.com/v1/messages';
@@ -450,8 +450,13 @@ export type AttachmentSaveInfo = {
450
450
  export async function notifyAgentSaveAttachment(info: AttachmentSaveInfo) {
451
451
  const { platform, service, owner, attachment, parsedContent } = info;
452
452
 
453
+ // Spreadsheets and PDFs are read by PAGING through readFileContent (grid rows +
454
+ // embedded photos / scanned page images), NOT inlined once - so they skip the inline
455
+ // server-extract and the agent is told to page the whole file (see pagedRead below).
456
+ const pagedRead = !parsedContent && isPagedReadFile(attachment.name, attachment.mime);
457
+
453
458
  // Client-parsed content wins over server-side extraction.
454
- const serverExtract = !parsedContent && isServerExtractable(attachment.name, attachment.mime);
459
+ const serverExtract = !parsedContent && !pagedRead && isServerExtractable(attachment.name, attachment.mime);
455
460
  const placeholder = serverExtract ? makeExtractPlaceholder(attachment.storagePath) : undefined;
456
461
  const extractContent: ExtractDirective[] | undefined =
457
462
  serverExtract && placeholder
@@ -466,7 +471,9 @@ export async function notifyAgentSaveAttachment(info: AttachmentSaveInfo) {
466
471
  ? { inlineContent: parsedContent }
467
472
  : placeholder
468
473
  ? { inlineContentPlaceholder: placeholder }
469
- : undefined,
474
+ : pagedRead
475
+ ? { pagedRead: true }
476
+ : undefined,
470
477
  );
471
478
 
472
479
  const systemPrompt = buildIndexingSystemPrompt({