@agent-native/core 0.51.5 → 0.51.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/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/templates-meta.js +1 -1
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +435 -3
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/provider-api/corpus-jobs-store.d.ts +95 -0
- package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -0
- package/dist/provider-api/corpus-jobs-store.js +394 -0
- package/dist/provider-api/corpus-jobs-store.js.map +1 -0
- package/dist/provider-api/corpus-jobs.d.ts +146 -0
- package/dist/provider-api/corpus-jobs.d.ts.map +1 -0
- package/dist/provider-api/corpus-jobs.js +1192 -0
- package/dist/provider-api/corpus-jobs.js.map +1 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +9 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth-marketing.js +4 -4
- package/dist/server/auth-marketing.js.map +1 -1
- package/docs/content/cloneable-saas.md +1 -1
- package/docs/content/getting-started.md +1 -1
- package/docs/content/local-file-mode.md +6 -1
- package/docs/content/template-analytics.md +0 -8
- package/docs/content/template-assets.md +0 -6
- package/docs/content/template-brain.md +0 -8
- package/docs/content/template-calendar.md +0 -8
- package/docs/content/template-clips.md +0 -8
- package/docs/content/template-content.md +27 -23
- package/docs/content/template-design.md +0 -6
- package/docs/content/template-forms.md +0 -10
- package/docs/content/template-mail.md +0 -8
- package/docs/content/template-plan.md +180 -0
- package/docs/content/template-slides.md +0 -8
- package/docs/content/template-videos.md +0 -8
- package/package.json +3 -1
|
@@ -110,6 +110,7 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
110
110
|
" Example: `const data = await providerFetch('<provider-id>', '/records', { query: { limit: 100 } });`",
|
|
111
111
|
" - `providerRequest(provider, path, init?)` — same authenticated call, but returns the full provider-api envelope with request, response status/headers, truncation, and body metadata.",
|
|
112
112
|
" - `providerFetchAll(provider, path, init?)` — generic pagination helper for cursor, page, and offset APIs. Pass `pagination: { itemsPath, cursorPath or nextCursorPath, cursorParam or cursorBodyPath, pageParam, offsetParam, pageSize, maxPages }`. Returns `{ items, pages, pageCount, itemCount, hasMore, lastCursor, stoppedReason }`.",
|
|
113
|
+
" - `providerSearchAll(provider, path, init?, options?)` — streaming search helper for broad provider corpora such as transcripts, messages, tickets, issues, notes, events, or documents. Use this before hand-written loops when searching many provider records for terms/phrases/regexes or proving absence. Pass the same `pagination` config as `providerFetchAll`, plus options like `{ query, queries, terms, regex, textPaths, idPaths, metadataPaths, maxHits }`. Returns structured hits with item ids, paths, snippets, page/item indexes, and coverage fields (`pageCount`, `itemCount`, `hasMore`, `stoppedReason`).",
|
|
113
114
|
" - `webFetch(url, init?)` — outbound HTTP request via the web-request action.",
|
|
114
115
|
" Returns `{ status, body }` where body is the response text.",
|
|
115
116
|
" Example: `const { body } = await webFetch('https://api.example.com/data');`",
|
|
@@ -376,8 +377,8 @@ function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools
|
|
|
376
377
|
// ---------------------------------------------------------------------------
|
|
377
378
|
/**
|
|
378
379
|
* Wrap the user's code in an ESM module that:
|
|
379
|
-
* 1. Defines `providerFetch`, `providerRequest`, `providerFetchAll`,
|
|
380
|
-
* `webFetch` helpers via the bridge.
|
|
380
|
+
* 1. Defines `providerFetch`, `providerRequest`, `providerFetchAll`,
|
|
381
|
+
* `providerSearchAll`, and `webFetch` helpers via the bridge.
|
|
381
382
|
* 2. Runs the user's code as top-level await in an async IIFE.
|
|
382
383
|
*/
|
|
383
384
|
function buildSandboxModule(userCode, bridgePort, bridgeToken) {
|
|
@@ -524,7 +525,7 @@ function _extractItems(page, itemsPath) {
|
|
|
524
525
|
}
|
|
525
526
|
if (Array.isArray(page)) return page;
|
|
526
527
|
if (!page || typeof page !== "object") return [];
|
|
527
|
-
for (const key of ["data", "results", "items", "records", "rows", "calls", "messages", "tickets", "issues", "deals"]) {
|
|
528
|
+
for (const key of ["data", "results", "items", "records", "rows", "calls", "callTranscripts", "transcripts", "messages", "tickets", "issues", "deals", "events", "notes", "documents", "entries", "objects"]) {
|
|
528
529
|
if (Array.isArray(page[key])) return page[key];
|
|
529
530
|
}
|
|
530
531
|
return [];
|
|
@@ -542,6 +543,437 @@ function _withoutProviderFetchAllOptions(init) {
|
|
|
542
543
|
return rest;
|
|
543
544
|
}
|
|
544
545
|
|
|
546
|
+
function _asArray(value) {
|
|
547
|
+
if (value === undefined || value === null) return [];
|
|
548
|
+
return Array.isArray(value) ? value : [value];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function _stringifySearchValue(value) {
|
|
552
|
+
if (typeof value === "string") return value;
|
|
553
|
+
if (value === undefined || value === null) return "";
|
|
554
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
555
|
+
return String(value);
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
return JSON.stringify(value);
|
|
559
|
+
} catch {
|
|
560
|
+
return String(value);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function _collectStrings(value, basePath = "", out = [], limit = 5000) {
|
|
565
|
+
if (out.length >= limit || value === undefined || value === null) return out;
|
|
566
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
567
|
+
out.push({ path: basePath || "$", text: String(value) });
|
|
568
|
+
return out;
|
|
569
|
+
}
|
|
570
|
+
if (Array.isArray(value)) {
|
|
571
|
+
for (let i = 0; i < value.length && out.length < limit; i++) {
|
|
572
|
+
_collectStrings(value[i], basePath ? basePath + "[" + i + "]" : "[" + i + "]", out, limit);
|
|
573
|
+
}
|
|
574
|
+
return out;
|
|
575
|
+
}
|
|
576
|
+
if (typeof value === "object") {
|
|
577
|
+
for (const key of Object.keys(value)) {
|
|
578
|
+
if (out.length >= limit) break;
|
|
579
|
+
_collectStrings(value[key], basePath ? basePath + "." + key : key, out, limit);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return out;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function _collectSearchStrings(item, textPaths, maxFieldsPerItem) {
|
|
586
|
+
const paths = _asArray(textPaths).filter((path) => typeof path === "string" && path.trim());
|
|
587
|
+
if (!paths.length) return _collectStrings(item, "", [], maxFieldsPerItem);
|
|
588
|
+
const out = [];
|
|
589
|
+
for (const path of paths) {
|
|
590
|
+
const value = _getByPath(item, path);
|
|
591
|
+
if (value !== undefined) _collectStrings(value, path, out, maxFieldsPerItem);
|
|
592
|
+
if (out.length >= maxFieldsPerItem) break;
|
|
593
|
+
}
|
|
594
|
+
return out;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function _firstValueByPath(value, paths) {
|
|
598
|
+
for (const path of paths) {
|
|
599
|
+
const found = _getByPath(value, path);
|
|
600
|
+
if (found !== undefined && found !== null && String(found) !== "") {
|
|
601
|
+
return { path, value: found };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const _DEFAULT_ID_PATHS = [
|
|
608
|
+
"id",
|
|
609
|
+
"callId",
|
|
610
|
+
"callID",
|
|
611
|
+
"call_id",
|
|
612
|
+
"call.id",
|
|
613
|
+
"call.metaData.id",
|
|
614
|
+
"metaData.id",
|
|
615
|
+
"metadata.id",
|
|
616
|
+
"recordId",
|
|
617
|
+
"record_id",
|
|
618
|
+
"objectId",
|
|
619
|
+
"object_id",
|
|
620
|
+
"ticketId",
|
|
621
|
+
"ticket_id",
|
|
622
|
+
"issueId",
|
|
623
|
+
"issue_id",
|
|
624
|
+
"messageId",
|
|
625
|
+
"message_id",
|
|
626
|
+
"conversationId",
|
|
627
|
+
"conversation_id",
|
|
628
|
+
"eventId",
|
|
629
|
+
"event_id",
|
|
630
|
+
"documentId",
|
|
631
|
+
"document_id",
|
|
632
|
+
"url",
|
|
633
|
+
"webUrl",
|
|
634
|
+
"permalink",
|
|
635
|
+
];
|
|
636
|
+
|
|
637
|
+
function _extractItemIdentity(item, idPaths) {
|
|
638
|
+
const paths = [
|
|
639
|
+
..._asArray(idPaths).filter((path) => typeof path === "string" && path.trim()),
|
|
640
|
+
..._DEFAULT_ID_PATHS,
|
|
641
|
+
];
|
|
642
|
+
const found = _firstValueByPath(item, paths);
|
|
643
|
+
if (!found) return { id: null, idPath: null };
|
|
644
|
+
return { id: _stringifySearchValue(found.value), idPath: found.path };
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function _extractMetadata(item, metadataPaths) {
|
|
648
|
+
const metadata = {};
|
|
649
|
+
for (const path of _asArray(metadataPaths)) {
|
|
650
|
+
if (typeof path !== "string" || !path.trim()) continue;
|
|
651
|
+
const value = _getByPath(item, path);
|
|
652
|
+
if (value !== undefined) metadata[path] = value;
|
|
653
|
+
}
|
|
654
|
+
return metadata;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function _makeSnippet(text, index, contextChars) {
|
|
658
|
+
const source = String(text);
|
|
659
|
+
const context = Math.max(20, Math.min(Number(contextChars) || 180, 1000));
|
|
660
|
+
const start = Math.max(0, index - context);
|
|
661
|
+
const end = Math.min(source.length, Math.max(index, 0) + context);
|
|
662
|
+
const prefix = start > 0 ? "..." : "";
|
|
663
|
+
const suffix = end < source.length ? "..." : "";
|
|
664
|
+
return (prefix + source.slice(start, end) + suffix).replace(/\\s+/g, " ").trim();
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function _normalizeFlags(flags, caseSensitive) {
|
|
668
|
+
const raw = typeof flags === "string" ? flags : "";
|
|
669
|
+
const allowed = raw.replace(/[^dgimsuvy]/g, "");
|
|
670
|
+
const withoutGlobalOrSticky = allowed.replace(/[gy]/g, "");
|
|
671
|
+
const withCase =
|
|
672
|
+
caseSensitive || /i/.test(withoutGlobalOrSticky)
|
|
673
|
+
? withoutGlobalOrSticky
|
|
674
|
+
: withoutGlobalOrSticky + "i";
|
|
675
|
+
return withCase + "g";
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function _normalizedSearchTerms(options) {
|
|
679
|
+
const explicitTerms = _asArray(options.terms)
|
|
680
|
+
.map((term) => String(term).trim())
|
|
681
|
+
.filter(Boolean);
|
|
682
|
+
if (explicitTerms.length) return explicitTerms;
|
|
683
|
+
if (options.matchMode === "allTerms" && typeof options.query === "string") {
|
|
684
|
+
return options.query
|
|
685
|
+
.split(/\\s+/)
|
|
686
|
+
.map((term) => term.trim())
|
|
687
|
+
.filter(Boolean);
|
|
688
|
+
}
|
|
689
|
+
return [];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function _findItemWideTermMatch(fields, options) {
|
|
693
|
+
const terms = _normalizedSearchTerms(options);
|
|
694
|
+
if (!terms.length || options.matchMode === "anyTerm") return null;
|
|
695
|
+
const caseSensitive = Boolean(options.caseSensitive);
|
|
696
|
+
const normalizedFields = fields.map((field) => ({
|
|
697
|
+
field,
|
|
698
|
+
haystack: caseSensitive ? String(field.text) : String(field.text).toLowerCase(),
|
|
699
|
+
}));
|
|
700
|
+
const termHits = terms.map((term) => {
|
|
701
|
+
const searchTerm = caseSensitive ? term : term.toLowerCase();
|
|
702
|
+
for (const entry of normalizedFields) {
|
|
703
|
+
const index = entry.haystack.indexOf(searchTerm);
|
|
704
|
+
if (index >= 0) return { term, field: entry.field, index };
|
|
705
|
+
}
|
|
706
|
+
return { term, field: null, index: -1 };
|
|
707
|
+
});
|
|
708
|
+
if (termHits.some((hit) => hit.index < 0 || !hit.field)) return null;
|
|
709
|
+
const first = termHits
|
|
710
|
+
.filter((hit) => hit.field)
|
|
711
|
+
.sort((a, b) => {
|
|
712
|
+
const fieldOrder = fields.indexOf(a.field) - fields.indexOf(b.field);
|
|
713
|
+
return fieldOrder || a.index - b.index;
|
|
714
|
+
})[0];
|
|
715
|
+
return {
|
|
716
|
+
field: first.field,
|
|
717
|
+
match: {
|
|
718
|
+
kind: "allTerms",
|
|
719
|
+
query: terms.join(" "),
|
|
720
|
+
index: first.index,
|
|
721
|
+
match: first.term,
|
|
722
|
+
},
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function _findSearchMatches(text, options, includeTerms = true) {
|
|
727
|
+
const source = String(text);
|
|
728
|
+
const caseSensitive = Boolean(options.caseSensitive);
|
|
729
|
+
const haystack = caseSensitive ? source : source.toLowerCase();
|
|
730
|
+
const maxMatchesPerField = _boundedNumber(options.maxMatchesPerField, 1000, 1, 100000);
|
|
731
|
+
const matches = [];
|
|
732
|
+
|
|
733
|
+
const addSubstring = (needle, label, kind) => {
|
|
734
|
+
if (needle === undefined || needle === null) return;
|
|
735
|
+
const rawNeedle = String(needle);
|
|
736
|
+
if (!rawNeedle) return;
|
|
737
|
+
const searchNeedle = caseSensitive ? rawNeedle : rawNeedle.toLowerCase();
|
|
738
|
+
let from = 0;
|
|
739
|
+
while (from <= haystack.length) {
|
|
740
|
+
const index = haystack.indexOf(searchNeedle, from);
|
|
741
|
+
if (index < 0) break;
|
|
742
|
+
matches.push({ kind, query: label ?? rawNeedle, index, match: source.slice(index, index + rawNeedle.length) });
|
|
743
|
+
from = index + Math.max(1, searchNeedle.length);
|
|
744
|
+
if (matches.length >= maxMatchesPerField) break;
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
if (options.regex) {
|
|
749
|
+
try {
|
|
750
|
+
const regex = new RegExp(String(options.regex), _normalizeFlags(options.regexFlags, caseSensitive));
|
|
751
|
+
let match;
|
|
752
|
+
while ((match = regex.exec(source)) && typeof match.index === "number") {
|
|
753
|
+
matches.push({ kind: "regex", query: String(options.regex), index: match.index, match: match[0] });
|
|
754
|
+
if (matches.length >= maxMatchesPerField) break;
|
|
755
|
+
if (match[0] === "") regex.lastIndex += 1;
|
|
756
|
+
}
|
|
757
|
+
} catch (err) {
|
|
758
|
+
throw new Error("providerSearchAll invalid regex: " + (err?.message || err));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
for (const query of _asArray(options.query).concat(_asArray(options.queries))) {
|
|
763
|
+
addSubstring(query, String(query), "query");
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const terms = includeTerms ? _normalizedSearchTerms(options) : [];
|
|
767
|
+
if (terms.length) {
|
|
768
|
+
const termHits = terms
|
|
769
|
+
.map((term) => {
|
|
770
|
+
const searchTerm = caseSensitive ? term : term.toLowerCase();
|
|
771
|
+
const index = haystack.indexOf(searchTerm);
|
|
772
|
+
return { term, index };
|
|
773
|
+
})
|
|
774
|
+
.filter((hit) => hit.index >= 0);
|
|
775
|
+
const mode = options.matchMode === "anyTerm" ? "anyTerm" : "allTerms";
|
|
776
|
+
if ((mode === "allTerms" && termHits.length === terms.length) || (mode === "anyTerm" && termHits.length > 0)) {
|
|
777
|
+
const first = termHits.sort((a, b) => a.index - b.index)[0];
|
|
778
|
+
matches.push({ kind: mode, query: terms.join(" "), index: first.index, match: first.term });
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return matches.sort((a, b) => a.index - b.index);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function _boundedNumber(value, defaultValue, min, max) {
|
|
786
|
+
const parsed = Number(value);
|
|
787
|
+
const finite = Number.isFinite(parsed) ? parsed : defaultValue;
|
|
788
|
+
return Math.max(min, Math.min(finite, max));
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function _hitKey(identity, path, query, index, pageIndex, pageItemIndex) {
|
|
792
|
+
const itemKey =
|
|
793
|
+
identity.id !== null && identity.id !== undefined
|
|
794
|
+
? "id:" + identity.id
|
|
795
|
+
: "page:" + String(pageIndex) + ":" + String(pageItemIndex);
|
|
796
|
+
return [itemKey, path ?? "", query ?? "", String(index ?? "")].join("\\n");
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Stream pages from a provider API and search item text structurally. This is
|
|
801
|
+
* for broad mention searches and absence checks where keeping every raw page
|
|
802
|
+
* in memory or hand-parsing JSON strings is brittle.
|
|
803
|
+
*/
|
|
804
|
+
async function providerSearchAll(provider, apiPath, init = {}, options = {}) {
|
|
805
|
+
const pagination = init.pagination || init.fetchAllPages || {};
|
|
806
|
+
const itemsPath = pagination.itemsPath || init.itemsPath || options.itemsPath;
|
|
807
|
+
const cursorPath = pagination.nextCursorPath || pagination.cursorPath;
|
|
808
|
+
const maxPagesRaw = Number(pagination.maxPages || init.maxPages || options.maxPages || 100);
|
|
809
|
+
const maxPages = Math.max(1, Math.min(Number.isFinite(maxPagesRaw) ? maxPagesRaw : 100, 500));
|
|
810
|
+
const maxHits = _boundedNumber(options.maxHits, 100, 1, 5000);
|
|
811
|
+
const maxHitsPerItem = _boundedNumber(options.maxHitsPerItem, 3, 1, 100);
|
|
812
|
+
const maxFieldsPerItem = _boundedNumber(options.maxFieldsPerItem, 5000, 1, 50000);
|
|
813
|
+
const contextChars = options.contextChars ?? options.snippetChars ?? 180;
|
|
814
|
+
const baseInit = _withoutProviderFetchAllOptions(init);
|
|
815
|
+
let query = _cloneJson(init.query || {});
|
|
816
|
+
let body = _cloneJson(init.body);
|
|
817
|
+
let pageNumber = Number(pagination.startPage || 1);
|
|
818
|
+
let offset = Number(pagination.startOffset || 0);
|
|
819
|
+
let lastCursor = null;
|
|
820
|
+
let stoppedReason = "completed";
|
|
821
|
+
let itemCount = 0;
|
|
822
|
+
let matchedItemCount = 0;
|
|
823
|
+
let totalHitCount = 0;
|
|
824
|
+
const hits = [];
|
|
825
|
+
const seenHitKeys = new Set();
|
|
826
|
+
let pageIndex = 0;
|
|
827
|
+
|
|
828
|
+
for (; pageIndex < maxPages; pageIndex++) {
|
|
829
|
+
if (pagination.pageParam) query = { ...(query || {}), [pagination.pageParam]: pageNumber };
|
|
830
|
+
if (pagination.offsetParam) query = { ...(query || {}), [pagination.offsetParam]: offset };
|
|
831
|
+
|
|
832
|
+
const page = await providerFetch(provider, apiPath, {
|
|
833
|
+
...baseInit,
|
|
834
|
+
query,
|
|
835
|
+
...(body !== undefined ? { body } : {}),
|
|
836
|
+
});
|
|
837
|
+
const nextCursor = cursorPath ? _getByPath(page, cursorPath) : undefined;
|
|
838
|
+
const hasNextCursor =
|
|
839
|
+
nextCursor !== undefined && nextCursor !== null && String(nextCursor) !== "";
|
|
840
|
+
if (hasNextCursor && lastCursor !== null && String(nextCursor) === String(lastCursor)) {
|
|
841
|
+
stoppedReason = "repeated-cursor";
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const pageItems = _extractItems(page, itemsPath);
|
|
846
|
+
itemCount += pageItems.length;
|
|
847
|
+
|
|
848
|
+
for (let pageItemIndex = 0; pageItemIndex < pageItems.length; pageItemIndex++) {
|
|
849
|
+
const item = pageItems[pageItemIndex];
|
|
850
|
+
const identity = _extractItemIdentity(item, options.idPaths);
|
|
851
|
+
const metadata = _extractMetadata(item, options.metadataPaths);
|
|
852
|
+
const fields = _collectSearchStrings(item, options.textPaths, maxFieldsPerItem);
|
|
853
|
+
let storedItemHitCount = 0;
|
|
854
|
+
let itemMatched = false;
|
|
855
|
+
|
|
856
|
+
const addHit = (field, match) => {
|
|
857
|
+
const key = _hitKey(identity, field.path, match.query, match.index, pageIndex, pageItemIndex);
|
|
858
|
+
if (seenHitKeys.has(key)) return false;
|
|
859
|
+
seenHitKeys.add(key);
|
|
860
|
+
totalHitCount += 1;
|
|
861
|
+
if (!itemMatched) {
|
|
862
|
+
matchedItemCount += 1;
|
|
863
|
+
itemMatched = true;
|
|
864
|
+
}
|
|
865
|
+
if (hits.length < maxHits && storedItemHitCount < maxHitsPerItem) {
|
|
866
|
+
storedItemHitCount += 1;
|
|
867
|
+
hits.push({
|
|
868
|
+
id: identity.id,
|
|
869
|
+
idPath: identity.idPath,
|
|
870
|
+
pageIndex,
|
|
871
|
+
pageItemIndex,
|
|
872
|
+
itemIndex: itemCount - pageItems.length + pageItemIndex,
|
|
873
|
+
path: field.path,
|
|
874
|
+
kind: match.kind,
|
|
875
|
+
query: match.query,
|
|
876
|
+
match: match.match,
|
|
877
|
+
snippet: _makeSnippet(field.text, match.index, contextChars),
|
|
878
|
+
...(Object.keys(metadata).length ? { metadata } : {}),
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
return true;
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
const itemWideTermMatch = _findItemWideTermMatch(fields, options);
|
|
885
|
+
if (itemWideTermMatch) {
|
|
886
|
+
addHit(itemWideTermMatch.field, itemWideTermMatch.match);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
for (const field of fields) {
|
|
890
|
+
const fieldMatches = _findSearchMatches(field.text, options, !itemWideTermMatch);
|
|
891
|
+
for (const match of fieldMatches) {
|
|
892
|
+
addHit(field, match);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (hasNextCursor) {
|
|
898
|
+
lastCursor = nextCursor;
|
|
899
|
+
if (pagination.cursorBodyPath) {
|
|
900
|
+
body = _setByPath(body || {}, pagination.cursorBodyPath, nextCursor);
|
|
901
|
+
} else if (pagination.cursorParam) {
|
|
902
|
+
query = { ...(query || {}), [pagination.cursorParam]: nextCursor };
|
|
903
|
+
} else {
|
|
904
|
+
stoppedReason = "cursor-found-without-destination";
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
lastCursor = null;
|
|
911
|
+
if (pagination.pageParam) {
|
|
912
|
+
if (pageItems.length === 0) {
|
|
913
|
+
stoppedReason = "empty-page";
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
pageNumber += 1;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (pagination.offsetParam) {
|
|
920
|
+
if (pageItems.length === 0) {
|
|
921
|
+
stoppedReason = "empty-page";
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
const step = Number(pagination.pageSize || pageItems.length);
|
|
925
|
+
if (!Number.isFinite(step) || step <= 0) {
|
|
926
|
+
stoppedReason = "invalid-page-size";
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
offset += step;
|
|
930
|
+
if (pagination.pageSize && pageItems.length < Number(pagination.pageSize)) {
|
|
931
|
+
stoppedReason = "short-page";
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const pageCount = pageIndex + (pageIndex < maxPages ? 1 : 0);
|
|
941
|
+
const hitPageOrOffsetLimit =
|
|
942
|
+
Boolean(pagination.pageParam || pagination.offsetParam) &&
|
|
943
|
+
stoppedReason === "completed" &&
|
|
944
|
+
pageCount >= maxPages;
|
|
945
|
+
const hasMore =
|
|
946
|
+
stoppedReason === "cursor-found-without-destination" ||
|
|
947
|
+
(lastCursor !== null && pageCount >= maxPages) || hitPageOrOffsetLimit;
|
|
948
|
+
if (hasMore && stoppedReason === "completed") stoppedReason = "max-pages";
|
|
949
|
+
|
|
950
|
+
return {
|
|
951
|
+
hits,
|
|
952
|
+
hitCount: hits.length,
|
|
953
|
+
totalHitCount,
|
|
954
|
+
truncatedHits: totalHitCount > hits.length,
|
|
955
|
+
matchedItemCount,
|
|
956
|
+
itemCount,
|
|
957
|
+
pageCount,
|
|
958
|
+
hasMore,
|
|
959
|
+
lastCursor,
|
|
960
|
+
stoppedReason,
|
|
961
|
+
searched: {
|
|
962
|
+
provider,
|
|
963
|
+
path: apiPath,
|
|
964
|
+
itemsPath: itemsPath || null,
|
|
965
|
+
textPaths: _asArray(options.textPaths),
|
|
966
|
+
idPaths: _asArray(options.idPaths),
|
|
967
|
+
query: options.query ?? null,
|
|
968
|
+
queries: _asArray(options.queries),
|
|
969
|
+
terms: _asArray(options.terms),
|
|
970
|
+
regex: options.regex ?? null,
|
|
971
|
+
matchMode: options.matchMode || (options.terms ? "allTerms" : "query"),
|
|
972
|
+
caseSensitive: Boolean(options.caseSensitive),
|
|
973
|
+
},
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
|
|
545
977
|
/**
|
|
546
978
|
* Fetch every page from a provider API using generic cursor, page-number, or
|
|
547
979
|
* offset pagination. Prefer this inside run-code when the answer depends on a
|