bunnyquery 1.14.0 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/engine.cjs CHANGED
@@ -73,6 +73,13 @@ function normalizeWebAccess(value) {
73
73
  function webAccessFrom(data) {
74
74
  return data ? normalizeWebAccess(data.web_access) : DEFAULT_WEB_ACCESS;
75
75
  }
76
+ var DEFAULT_SKIP_DUPLICATE_FILES = true;
77
+ function normalizeSkipDuplicateFiles(value) {
78
+ return value !== false;
79
+ }
80
+ function skipDuplicateFilesFrom(data) {
81
+ return data ? normalizeSkipDuplicateFiles(data.skip_duplicate_files) : DEFAULT_SKIP_DUPLICATE_FILES;
82
+ }
76
83
  var CHAT_GREETING_MAX_LENGTH = 400;
77
84
  function normalizeChatGreeting(value) {
78
85
  if (typeof value !== "string") return "";
@@ -159,6 +166,9 @@ function projectChatGreeting(service) {
159
166
  function projectWebAccess(service) {
160
167
  return webAccessFrom(cachedProjectSettings(service));
161
168
  }
169
+ function projectSkipDuplicateFiles(service) {
170
+ return skipDuplicateFilesFrom(cachedProjectSettings(service));
171
+ }
162
172
  function setProjectSettings(service, data) {
163
173
  if (!service) return;
164
174
  cache.set(service, { data: data || null, settled: true, inflight: null });
@@ -238,16 +248,29 @@ function liveStreamingRealtimeEnabled() {
238
248
  function liveStreamingEnabled() {
239
249
  return _config?.liveStreaming === true;
240
250
  }
251
+ function underCeiling(projectSetting, option, override) {
252
+ if (!projectSetting) return false;
253
+ if (option === false) return false;
254
+ if (override === false) return false;
255
+ return true;
256
+ }
241
257
  function webAccessEnabled(service) {
242
258
  const opt = _config ? _config.webAccess : void 0;
243
- if (typeof opt === "boolean") return opt;
244
- return webAccessFrom(service ? cachedProjectSettings(service) : null);
259
+ return underCeiling(webAccessFrom(service ? cachedProjectSettings(service) : null), opt);
245
260
  }
246
- function resolveWebAccess(service) {
261
+ function resolveWebAccess(service, override) {
247
262
  const opt = _config ? _config.webAccess : void 0;
248
- if (typeof opt === "boolean") return Promise.resolve(opt);
249
- if (!service) return Promise.resolve(false);
250
- return loadProjectSettings(service).then(webAccessFrom, () => false);
263
+ if (!service) return Promise.resolve(underCeiling(webAccessFrom(null), opt, override));
264
+ return loadProjectSettings(service).then(webAccessFrom, () => false).then((setting) => underCeiling(setting, opt, override));
265
+ }
266
+ function skipDuplicateFilesEnabled(service) {
267
+ const opt = _config ? _config.skipDuplicateFiles : void 0;
268
+ return underCeiling(skipDuplicateFilesFrom(service ? cachedProjectSettings(service) : null), opt);
269
+ }
270
+ function resolveSkipDuplicateFiles(service) {
271
+ const opt = _config ? _config.skipDuplicateFiles : void 0;
272
+ if (!service) return Promise.resolve(underCeiling(skipDuplicateFilesFrom(null), opt));
273
+ return loadProjectSettings(service).then(skipDuplicateFilesFrom, () => skipDuplicateFilesFrom(null)).then((setting) => underCeiling(setting, opt));
251
274
  }
252
275
  function streamRecoveryEnabled() {
253
276
  if (!_config) return false;
@@ -263,6 +286,54 @@ function pollOpt() {
263
286
  return p === void 0 ? {} : { poll: p };
264
287
  }
265
288
 
289
+ // src/engine/duplicates.ts
290
+ var DUPLICATE_LOOKUP_MAX_CANDIDATES = 25;
291
+ var SRC_PREFIX = "src::";
292
+ function isDuplicateCandidateId(uniqueId, name) {
293
+ if (typeof uniqueId !== "string" || !name) return false;
294
+ if (uniqueId.indexOf(SRC_PREFIX) !== 0) return false;
295
+ if (uniqueId === SRC_PREFIX + name) return true;
296
+ const tail = "/" + name;
297
+ return uniqueId.length > SRC_PREFIX.length + tail.length && uniqueId.slice(-tail.length) === tail;
298
+ }
299
+ function listOf(res) {
300
+ if (!res) return [];
301
+ if (Array.isArray(res)) return res;
302
+ return Array.isArray(res.list) ? res.list : [];
303
+ }
304
+ async function findDuplicateUpload(skapi, file, userId, opts) {
305
+ try {
306
+ if (!skapi || typeof skapi.getUniqueId !== "function" || typeof skapi.getRecords !== "function") return null;
307
+ const name = file && typeof file.name === "string" ? file.name : "";
308
+ const size = file && typeof file.size === "number" ? file.size : NaN;
309
+ if (!name || !(size >= 0) || !userId) return null;
310
+ const service = opts && opts.service ? { service: opts.service } : {};
311
+ const found = listOf(await skapi.getUniqueId(Object.assign({ unique_id: name, condition: "<=" }, service)));
312
+ const ids = [];
313
+ for (const item of found) {
314
+ const id = item && typeof item === "object" ? item.unique_id : item;
315
+ if (isDuplicateCandidateId(id, name) && ids.indexOf(id) === -1) ids.push(id);
316
+ if (ids.length >= DUPLICATE_LOOKUP_MAX_CANDIDATES) break;
317
+ }
318
+ for (const uniqueId of ids) {
319
+ let record = null;
320
+ try {
321
+ record = listOf(await skapi.getRecords(Object.assign({ unique_id: uniqueId }, service)))[0] || null;
322
+ } catch {
323
+ continue;
324
+ }
325
+ if (!record || typeof record !== "object") continue;
326
+ const data = record.data && typeof record.data === "object" ? record.data : null;
327
+ if (!data || data.size_bytes !== size) continue;
328
+ if (record.user_id !== userId) continue;
329
+ return { storagePath: uniqueId.slice(SRC_PREFIX.length), uniqueId, record };
330
+ }
331
+ return null;
332
+ } catch {
333
+ return null;
334
+ }
335
+ }
336
+
266
337
  // src/engine/office.ts
267
338
  var OFFICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
268
339
  "doc",
@@ -675,6 +746,10 @@ function buildRenderMeta(attachment) {
675
746
  ` : "") + `- access group (use this for EVERY record you write for this file): ${indexingAccessGroup(attachment)}
676
747
  `;
677
748
  }
749
+ var PAGE_TEXT_TABLE = "page_texts";
750
+ var PAGE_TEXT_RULE = `PAGE TEXT for pages WITHOUT a text layer: for EVERY page whose label (its first label, when the page is split into strips) carries NO "Embedded text layer" block, ALSO post exactly ONE record in table "${PAGE_TEXT_TABLE}" with data {"page": <its document page number from the label, 1-based>, "page_text": "<the page's complete text exactly as printed, verbatim, in reading order, nothing summarised or left out>"}, reference "src::" + the storage path above, in addition to the records above. A page WITH an embedded text layer gets NO such record: its text is already stored.
751
+
752
+ `;
678
753
  function buildRenderDatafy(placeholder) {
679
754
  return `
680
755
  ${placeholder}
@@ -685,7 +760,7 @@ Each image is preceded by a label giving its DOCUMENT PAGE number. That label is
685
760
 
686
761
  Transcribe COMPLETELY, not representatively. A table with twenty rows gets twenty records, not a sample of the first few - if a page has more rows than you can save comfortably, still save them all rather than summarising. Where a page carries an embedded text layer it is quoted above that page's image: it is the exact text and should be preferred over reading the pixels, with the image used for layout, tables, stamps and handwriting.
687
762
 
688
- 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.`;
763
+ ` + PAGE_TEXT_RULE + `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.`;
689
764
  }
690
765
  function buildIndexingWindowMessage(attachment, placeholder, isContinuation, positionLabel) {
691
766
  const src = `src::${attachment.storagePath}`;
@@ -1227,7 +1302,7 @@ var RETRIEVAL_NOTE_PREFIX = "[Retrieval note, not shown to the user";
1227
1302
  var FILE_KEYS = ["path", "storage_path", "storagePath", "file", "file_path", "filePath"];
1228
1303
  var TABLE_KEYS = ["table_name", "tableName"];
1229
1304
  var GROUP_KEYS = ["access_group", "accessGroup"];
1230
- var SRC_PREFIX = "src::";
1305
+ var SRC_PREFIX2 = "src::";
1231
1306
  var RESULT_WALK_MAX_NODES = 400;
1232
1307
  var RESULT_WALK_MAX_DEPTH = 5;
1233
1308
  function isWebToolName(name) {
@@ -1256,8 +1331,8 @@ function pushHit(list, path, page) {
1256
1331
  }
1257
1332
  function pathFromSrcId(value) {
1258
1333
  if (typeof value !== "string") return null;
1259
- if (value.indexOf(SRC_PREFIX) !== 0) return null;
1260
- var rest = value.slice(SRC_PREFIX.length).trim();
1334
+ if (value.indexOf(SRC_PREFIX2) !== 0) return null;
1335
+ var rest = value.slice(SRC_PREFIX2.length).trim();
1261
1336
  return rest ? rest : null;
1262
1337
  }
1263
1338
  function parseJsonLoose(value) {
@@ -1456,6 +1531,9 @@ var CONTEXT_WINDOW_BY_MODEL = {
1456
1531
  "claude-haiku-4-5": 2e5,
1457
1532
  "claude-3-5-sonnet": 2e5,
1458
1533
  // openai, exact ids
1534
+ // gpt-6.1: OpenAI's listing carries no window; taken as the gpt-6 family's until a
1535
+ // published figure says otherwise (2026-10-12, the default model).
1536
+ "gpt-6.1-sol": 105e4,
1459
1537
  "gpt-6-astra": 105e4,
1460
1538
  "gpt-6-sol": 105e4,
1461
1539
  "gpt-6-luna": 105e4,
@@ -1475,6 +1553,7 @@ var CONTEXT_WINDOW_BY_MODEL = {
1475
1553
  "claude-opus": 1e6,
1476
1554
  "claude-sonnet": 1e6,
1477
1555
  "claude-haiku": 2e5,
1556
+ "gpt-6.1": 105e4,
1478
1557
  "gpt-6": 105e4,
1479
1558
  "gpt-5.6": 105e4,
1480
1559
  "gpt-5": 128e3
@@ -1492,6 +1571,7 @@ var MAX_OUTPUT_BY_MODEL = {
1492
1571
  "claude-haiku-4-5": 64e3,
1493
1572
  "claude-3-5-sonnet": 8e3,
1494
1573
  // openai
1574
+ "gpt-6.1-sol": 128e3,
1495
1575
  "gpt-6-astra": 128e3,
1496
1576
  "gpt-6-sol": 128e3,
1497
1577
  "gpt-6-luna": 128e3,
@@ -1511,6 +1591,7 @@ var MAX_OUTPUT_BY_MODEL = {
1511
1591
  "claude-opus": 128e3,
1512
1592
  "claude-sonnet": 64e3,
1513
1593
  "claude-haiku": 64e3,
1594
+ "gpt-6.1": 128e3,
1514
1595
  "gpt-6": 128e3,
1515
1596
  "gpt-5.6": 128e3,
1516
1597
  "gpt-5": 128e3
@@ -2717,8 +2798,8 @@ var webToolsForChat = (platform, webAccess) => {
2717
2798
  return [openaiWebSearchTool()] ;
2718
2799
  };
2719
2800
  var MCP_NAME = "BunnyQuery";
2720
- var DEFAULT_CLAUDE_MODEL = "claude-sonnet-5";
2721
- var DEFAULT_OPENAI_MODEL = "gpt-5.6-luna";
2801
+ var DEFAULT_CLAUDE_MODEL = "claude-sonnet-5-5";
2802
+ var DEFAULT_OPENAI_MODEL = "gpt-6.1-sol";
2722
2803
  var mcpUrl = () => chatEngineConfig().mcpBaseUrl;
2723
2804
  function withMcpParams(base, params) {
2724
2805
  if (!base) return base;
@@ -2920,7 +3001,7 @@ async function callClaudeWithMcp({
2920
3001
  fileUrls,
2921
3002
  webAccess
2922
3003
  }) {
2923
- const web = typeof webAccess === "boolean" ? webAccess : await resolveWebAccess(service);
3004
+ const web = await resolveWebAccess(service, typeof webAccess === "boolean" ? webAccess : void 0);
2924
3005
  const mcpServerDefinition = {
2925
3006
  type: "url",
2926
3007
  name: mcpServer.name,
@@ -8509,11 +8590,26 @@ var ChatSession = class {
8509
8590
  if (!total) return Promise.reject(new Error("Empty attachment"));
8510
8591
  var urls = [];
8511
8592
  var anyIndexFailed = false;
8593
+ var reusedCount = 0;
8594
+ att.reused = false;
8512
8595
  var chain = Promise.resolve();
8596
+ var findDuplicate = function(member) {
8597
+ if (typeof self.host.findDuplicateUpload !== "function") return Promise.resolve(null);
8598
+ var storedName = String(member.storagePath || "").split("/").filter(Boolean).pop() || member.file.name;
8599
+ return resolveSkipDuplicateFiles(id.projectId).then(function(on) {
8600
+ if (!on) return null;
8601
+ return Promise.resolve(self.host.findDuplicateUpload({ name: storedName, size: member.file.size })).then(function(hit) {
8602
+ return hit && typeof hit.storagePath === "string" && hit.storagePath ? hit.storagePath : null;
8603
+ });
8604
+ }).catch(function() {
8605
+ return null;
8606
+ });
8607
+ };
8513
8608
  members.forEach(function(member, idx) {
8514
8609
  chain = chain.then(function() {
8515
8610
  var hadExists = false;
8516
8611
  var skipped = false;
8612
+ var duplicateOf = null;
8517
8613
  var existedBefore = false;
8518
8614
  var onProg = function(p) {
8519
8615
  if (p && p.total) {
@@ -8532,7 +8628,14 @@ var ChatSession = class {
8532
8628
  }
8533
8629
  });
8534
8630
  };
8535
- return doMemberUpload(true).catch(function(err) {
8631
+ return findDuplicate(member).then(function(dup) {
8632
+ if (dup) {
8633
+ duplicateOf = dup;
8634
+ console.log("[chat-engine] skipping an upload the user already has:", member.storagePath, "->", dup);
8635
+ return;
8636
+ }
8637
+ return doMemberUpload(true);
8638
+ }).catch(function(err) {
8536
8639
  var code = err && (err.code || err.body && err.body.code);
8537
8640
  var msg = err && (err.message || err.body && err.body.message || (typeof err === "string" ? err : ""));
8538
8641
  var isExists = code === "EXISTS" || msg && /exist/i.test(msg);
@@ -8552,9 +8655,20 @@ var ChatSession = class {
8552
8655
  });
8553
8656
  }).then(function() {
8554
8657
  if (skipped) return;
8555
- return self.host.getTemporaryUrl(member.storagePath);
8658
+ return self.host.getTemporaryUrl(duplicateOf || member.storagePath);
8556
8659
  }).then(function(url) {
8557
8660
  if (skipped) return;
8661
+ if (duplicateOf) {
8662
+ urls.push({ name: member.relPath, url, storagePath: duplicateOf });
8663
+ reusedCount += 1;
8664
+ if (att.kind !== "folder") {
8665
+ att.uploadedUrl = url;
8666
+ att.storagePath = duplicateOf;
8667
+ att.reused = true;
8668
+ } else att.reusedCount = reusedCount;
8669
+ self.host.renderAttachmentChips();
8670
+ return;
8671
+ }
8558
8672
  urls.push({ name: member.relPath, url, storagePath: member.storagePath });
8559
8673
  if (att.kind !== "folder") {
8560
8674
  att.uploadedUrl = url;
@@ -9195,8 +9309,10 @@ exports.DATA_TOOL_NAMES = DATA_TOOL_NAMES;
9195
9309
  exports.DEFAULT_CLAUDE_MODEL = DEFAULT_CLAUDE_MODEL;
9196
9310
  exports.DEFAULT_CONTEXT_WINDOW = DEFAULT_CONTEXT_WINDOW;
9197
9311
  exports.DEFAULT_OPENAI_MODEL = DEFAULT_OPENAI_MODEL;
9312
+ exports.DEFAULT_SKIP_DUPLICATE_FILES = DEFAULT_SKIP_DUPLICATE_FILES;
9198
9313
  exports.DEFAULT_UPLOAD_ACCESS_GROUP = DEFAULT_UPLOAD_ACCESS_GROUP;
9199
9314
  exports.DEFAULT_WEB_ACCESS = DEFAULT_WEB_ACCESS;
9315
+ exports.DUPLICATE_LOOKUP_MAX_CANDIDATES = DUPLICATE_LOOKUP_MAX_CANDIDATES;
9200
9316
  exports.EMPTY_INDEXING_REPLY = EMPTY_INDEXING_REPLY;
9201
9317
  exports.EXPIRED_ATTACHMENT_URL_HOST = EXPIRED_ATTACHMENT_URL_HOST;
9202
9318
  exports.EXPIRED_ATTACHMENT_URL_ORIGIN = EXPIRED_ATTACHMENT_URL_ORIGIN;
@@ -9228,6 +9344,7 @@ exports.MIN_INPUT_TOKEN_BUDGET = MIN_INPUT_TOKEN_BUDGET;
9228
9344
  exports.MIN_PER_REQUEST_INPUT_CAP = MIN_PER_REQUEST_INPUT_CAP;
9229
9345
  exports.NO_DATA_TOOL_MARK = NO_DATA_TOOL_MARK;
9230
9346
  exports.OUTPUT_TOKEN_RESERVE = OUTPUT_TOKEN_RESERVE;
9347
+ exports.PAGE_TEXT_TABLE = PAGE_TEXT_TABLE;
9231
9348
  exports.POLL_INTERVAL = POLL_INTERVAL;
9232
9349
  exports.PRESIGN_SAFETY_MARGIN_MS = PRESIGN_SAFETY_MARGIN_MS;
9233
9350
  exports.PREVIEWABLE_IMAGE_CONTENT_TYPES = PREVIEWABLE_IMAGE_CONTENT_TYPES;
@@ -9316,6 +9433,7 @@ exports.fetchLiveIndexingKeys = fetchLiveIndexingKeys;
9316
9433
  exports.fillHistoryViewport = fillHistoryViewport;
9317
9434
  exports.filterListByClearHorizon = filterListByClearHorizon;
9318
9435
  exports.findAttachmentParser = findAttachmentParser;
9436
+ exports.findDuplicateUpload = findDuplicateUpload;
9319
9437
  exports.formatChatTimestamp = formatChatTimestamp;
9320
9438
  exports.formatDuration = formatDuration;
9321
9439
  exports.formatRetrievalNote = formatRetrievalNote;
@@ -9340,6 +9458,7 @@ exports.isAuthExpiredError = isAuthExpiredError;
9340
9458
  exports.isBgIndexingQueue = isBgIndexingQueue;
9341
9459
  exports.isCsrStatusEnvelope = isCsrStatusEnvelope;
9342
9460
  exports.isDataToolName = isDataToolName;
9461
+ exports.isDuplicateCandidateId = isDuplicateCandidateId;
9343
9462
  exports.isErrorResponseBody = isErrorResponseBody;
9344
9463
  exports.isHttpUrlLike = isHttpUrlLike;
9345
9464
  exports.isImageVisionFile = isImageVisionFile;
@@ -9373,6 +9492,7 @@ exports.normalizeAttachmentPathCandidate = normalizeAttachmentPathCandidate;
9373
9492
  exports.normalizeChatGreeting = normalizeChatGreeting;
9374
9493
  exports.normalizeExt = normalizeExt;
9375
9494
  exports.normalizeProjectAccessSetting = normalizeProjectAccessSetting;
9495
+ exports.normalizeSkipDuplicateFiles = normalizeSkipDuplicateFiles;
9376
9496
  exports.normalizeTextContent = normalizeTextContent;
9377
9497
  exports.normalizeTrailingInlineToken = normalizeTrailingInlineToken;
9378
9498
  exports.normalizeUploadAccessGroup = normalizeUploadAccessGroup;
@@ -9395,6 +9515,7 @@ exports.projectAccessSetting = projectAccessSetting;
9395
9515
  exports.projectAsksUploadAccess = projectAsksUploadAccess;
9396
9516
  exports.projectChatGreeting = projectChatGreeting;
9397
9517
  exports.projectSettingsSettled = projectSettingsSettled;
9518
+ exports.projectSkipDuplicateFiles = projectSkipDuplicateFiles;
9398
9519
  exports.projectUploadAccessGroup = projectUploadAccessGroup;
9399
9520
  exports.projectWebAccess = projectWebAccess;
9400
9521
  exports.providerBodyWasRead = providerBodyWasRead;
@@ -9406,6 +9527,7 @@ exports.renderInlineLinkHtml = renderInlineLinkHtml;
9406
9527
  exports.repairUrlEntities = repairUrlEntities;
9407
9528
  exports.repairUrlWhitespace = repairUrlWhitespace;
9408
9529
  exports.resolveImagePreviewUrl = resolveImagePreviewUrl;
9530
+ exports.resolveSkipDuplicateFiles = resolveSkipDuplicateFiles;
9409
9531
  exports.resolveWebAccess = resolveWebAccess;
9410
9532
  exports.retrievalNoteFromText = retrievalNoteFromText;
9411
9533
  exports.retrievalNoteOfAnswer = retrievalNoteOfAnswer;
@@ -9416,6 +9538,8 @@ exports.setProjectContextWindow = setProjectContextWindow;
9416
9538
  exports.setProjectSettings = setProjectSettings;
9417
9539
  exports.shouldRescueInFlightMessage = shouldRescueInFlightMessage;
9418
9540
  exports.skapiSupportsStreaming = skapiSupportsStreaming;
9541
+ exports.skipDuplicateFilesEnabled = skipDuplicateFilesEnabled;
9542
+ exports.skipDuplicateFilesFrom = skipDuplicateFilesFrom;
9419
9543
  exports.streamRecoveryEnabled = streamRecoveryEnabled;
9420
9544
  exports.streamRecoveryLabels = streamRecoveryLabels;
9421
9545
  exports.streamRecoveryPhase = streamRecoveryPhase;