@mvriu5/payload-ai 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -0
- package/dist/ai/promptCaching.d.ts +23 -0
- package/dist/ai/promptCaching.js +120 -0
- package/dist/ai/proposalRepair.d.ts +30 -0
- package/dist/ai/proposalRepair.js +76 -0
- package/dist/ai/sensitiveData.d.ts +1 -0
- package/dist/ai/sensitiveData.js +3 -1
- package/dist/components/ai-input/AIInput.js +35 -5
- package/dist/components/ai-input/AIInput.module.css +49 -0
- package/dist/components/generate-field/GenerateField.d.ts +11 -0
- package/dist/components/generate-field/GenerateField.js +109 -0
- package/dist/components/generate-field/GenerateField.module.css +39 -0
- package/dist/components/generate-field/richText.d.ts +29 -0
- package/dist/components/generate-field/richText.js +30 -0
- package/dist/components/translate-document/TranslateDocumentButton.d.ts +2 -0
- package/dist/components/translate-document/TranslateDocumentButton.js +111 -0
- package/dist/components/translate-document/TranslateDocumentButton.module.css +11 -0
- package/dist/exports/client.d.ts +4 -0
- package/dist/exports/client.js +4 -0
- package/dist/handlers/chatHandler.d.ts +1 -0
- package/dist/handlers/chatHandler.js +464 -138
- package/dist/handlers/generateFieldHandler.d.ts +14 -0
- package/dist/handlers/generateFieldHandler.js +144 -0
- package/dist/handlers/translateDocumentHandler.d.ts +14 -0
- package/dist/handlers/translateDocumentHandler.js +186 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +112 -4
- package/dist/payload/documentTranslation.d.ts +15 -0
- package/dist/payload/documentTranslation.js +120 -0
- package/dist/payload/textFieldGeneration.d.ts +26 -0
- package/dist/payload/textFieldGeneration.js +99 -0
- package/dist/payload/toolFieldSelection.d.ts +25 -0
- package/dist/payload/toolFieldSelection.js +118 -0
- package/dist/payload/toolSchemas.d.ts +7 -0
- package/dist/payload/toolSchemas.js +117 -0
- package/package.json +3 -3
|
@@ -3,16 +3,17 @@ import { z } from "zod";
|
|
|
3
3
|
import { signAIActionProposal } from "../ai/proposalSigning.js";
|
|
4
4
|
import { isAIProvider } from "../ai/providerOptions.js";
|
|
5
5
|
import { getModel, getProviderConfig } from "../ai/providerRuntime.js";
|
|
6
|
-
import {
|
|
6
|
+
import { createCachedSystemMessages, createPromptCacheProviderOptions, splitPromptCacheContext } from "../ai/promptCaching.js";
|
|
7
|
+
import { compactProposalRepairIssues, createProposalRepairTracker } from "../ai/proposalRepair.js";
|
|
8
|
+
import { containsSensitiveData, isSensitiveKey, redactSensitiveData } from "../ai/sensitiveData.js";
|
|
7
9
|
import { getExceededTokenUsageLimit, recordTokenUsage } from "../ai/tokenUsage.js";
|
|
8
10
|
import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
|
|
9
11
|
import { prepareProposalWriteData } from "../payload/proposalData.js";
|
|
10
12
|
import { buildPromptWithMentionContext, collectBlocks, describeCollectionLikeConfig, describeCollectionLikeSummary, getAllowedCollectionSlugs, getMentionContext } from "../payload/schemaContext.js";
|
|
11
13
|
import { getLogPreview, logHandlerEvent } from "../payload/logging.js";
|
|
12
14
|
import { getOptionValue, getSafeProposalLabel, hasLocalizedData, hasValueAtPath, isRecord, setValueAtPath } from "../payload/shared.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
15
|
+
import { createLocalizedPayloadDataSchema, createPayloadDataSchema, genericPayloadDataSchema } from "../payload/toolSchemas.js";
|
|
16
|
+
import { createToolFieldNamesSchema, resolveToolFieldSelection } from "../payload/toolFieldSelection.js";
|
|
16
17
|
const e2eModeEnabled = ()=>process.env.PAYLOAD_AI_E2E_MODE === "true";
|
|
17
18
|
const createSSEEventStream = (events)=>{
|
|
18
19
|
const encoder = new TextEncoder();
|
|
@@ -578,12 +579,16 @@ const getMediaAttachmentContext = async ({ allowedCollectionsBySlug, attachments
|
|
|
578
579
|
if (seen.has(key)) continue;
|
|
579
580
|
const collectionConfig = allowedCollectionsBySlug.get(attachment.collection);
|
|
580
581
|
if (!collectionConfig?.upload) continue;
|
|
582
|
+
const fieldSelection = resolveToolFieldSelection({
|
|
583
|
+
config: collectionConfig
|
|
584
|
+
});
|
|
581
585
|
const doc = await req.payload.findByID({
|
|
582
586
|
collection: attachment.collection,
|
|
583
|
-
depth:
|
|
587
|
+
depth: fieldSelection.depth,
|
|
584
588
|
id: attachment.id,
|
|
585
589
|
overrideAccess: false,
|
|
586
|
-
req
|
|
590
|
+
req,
|
|
591
|
+
select: fieldSelection.select
|
|
587
592
|
}).catch(()=>null);
|
|
588
593
|
if (!doc) continue;
|
|
589
594
|
seen.add(key);
|
|
@@ -601,26 +606,6 @@ const getMediaAttachmentContext = async ({ allowedCollectionsBySlug, attachments
|
|
|
601
606
|
}
|
|
602
607
|
return contexts;
|
|
603
608
|
};
|
|
604
|
-
const formatProposalIssuesForRetry = (issues)=>{
|
|
605
|
-
return issues.slice(0, 6).map((issue)=>{
|
|
606
|
-
switch(issue.code){
|
|
607
|
-
case "invalid_block_type":
|
|
608
|
-
return `${issue.path}: use an exact blockType from the schema`;
|
|
609
|
-
case "invalid_blocks":
|
|
610
|
-
return `${issue.path}: blocks fields must be arrays of objects with blockType and exact field names`;
|
|
611
|
-
case "invalid_array":
|
|
612
|
-
return `${issue.path}: array fields must be arrays of complete objects`;
|
|
613
|
-
case "missing_required_field":
|
|
614
|
-
return `${issue.path}: required field missing`;
|
|
615
|
-
case "unknown_field":
|
|
616
|
-
return `${issue.path}: unknown field, use exact schema field names only`;
|
|
617
|
-
case "invalid_relationship":
|
|
618
|
-
return `${issue.path}: use relationship IDs or { relationTo, value }, not free text`;
|
|
619
|
-
default:
|
|
620
|
-
return `${issue.path}: ${issue.message}`;
|
|
621
|
-
}
|
|
622
|
-
}).join("; ");
|
|
623
|
-
};
|
|
624
609
|
const createCollectionAliasMap = (collections)=>{
|
|
625
610
|
const aliasMap = new Map();
|
|
626
611
|
const addAlias = (alias, slug)=>{
|
|
@@ -663,34 +648,139 @@ const getLikelyCollectionMatches = ({ aliasMap, prompt })=>{
|
|
|
663
648
|
...matches
|
|
664
649
|
];
|
|
665
650
|
};
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
651
|
+
const normalizeIntentText = (prompt)=>prompt.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
|
|
652
|
+
const deleteIntentPattern = /\b(?:delete|remove|erase|destroy|discard|purge|loesch\w*|losch\w*|entfern\w*|rausnehm\w*|wegnehm\w*|verwerf\w*)\b/;
|
|
653
|
+
const createIntentPattern = /\b(?:create|build|generate|draft|compose|author|write|make|new|erstell\w*|anleg\w*|erzeug\w*|generier\w*|entwerf\w*|verfass\w*|schreib\w*|neu)\b/;
|
|
654
|
+
const updateIntentPattern = /\b(?:update|edit|change|modify|revise|refine|rewrite|translate|replace|rename|correct|fix|set|publish|unpublish|aktualisier\w*|aender\w*|bearbeit\w*|anpass\w*|ueberarbeit\w*|uberarbeit\w*|umschreib\w*|uebersetz\w*|ubersetz\w*|ersetz\w*|umbenenn\w*|korrigier\w*|reparier\w*|setz\w*|veroeffentlich\w*|veroffentlich\w*|zurueckzieh\w*|zuruckzieh\w*)\b/;
|
|
655
|
+
const additiveIntentPattern = /\b(?:add|insert|append|attach|assign|link|include|extend|ergaenz\w*|erganz\w*|hinzufueg\w*|hinzufug\w*|einfueg\w*|einfug\w*|anhaeng\w*|anhang\w*|zuweis\w*|verknuepf\w*|verknupf\w*)\b/;
|
|
656
|
+
const searchIntentPattern = /\b(?:search|find|lookup|locate|list|show|fetch|browse|query|such\w*|find\w*|nachschlag\w*|auflist\w*|anzeig\w*|zeig\w*|hol\w*|durchsuch\w*)\b/;
|
|
657
|
+
const separableDeleteIntentPattern = /\b(?:nimm|nehm\w*)\b(?:\s+\w+){0,8}\s+(?:raus|weg)\b/;
|
|
658
|
+
const separableUpdateIntentPattern = /\b(?:pass(?:e|t|en|st)?\b(?:\s+\w+){0,8}\s+an|fueg\w*\b(?:\s+\w+){0,8}\s+(?:ein|hinzu)|haeng\w*\b(?:\s+\w+){0,8}\s+an|weis\w*\b(?:\s+\w+){0,8}\s+zu)\b/;
|
|
659
|
+
const getChatIntent = ({ hasCurrentDocument, hasCurrentGlobal, prompt })=>{
|
|
660
|
+
const normalizedPrompt = normalizeIntentText(prompt);
|
|
661
|
+
if (deleteIntentPattern.test(normalizedPrompt) || separableDeleteIntentPattern.test(normalizedPrompt)) {
|
|
662
|
+
return hasCurrentGlobal ? "read" : "delete";
|
|
663
|
+
}
|
|
664
|
+
if (createIntentPattern.test(normalizedPrompt)) {
|
|
665
|
+
if (hasCurrentGlobal) return "updateGlobal";
|
|
666
|
+
if (hasCurrentDocument) return "update";
|
|
667
|
+
return "create";
|
|
668
|
+
}
|
|
669
|
+
if (updateIntentPattern.test(normalizedPrompt) || additiveIntentPattern.test(normalizedPrompt) || separableUpdateIntentPattern.test(normalizedPrompt)) {
|
|
670
|
+
return hasCurrentGlobal ? "updateGlobal" : "update";
|
|
671
|
+
}
|
|
672
|
+
if (searchIntentPattern.test(normalizedPrompt)) {
|
|
673
|
+
return "search";
|
|
674
|
+
}
|
|
675
|
+
return "read";
|
|
669
676
|
};
|
|
670
|
-
const getIntentToolChoice = (
|
|
671
|
-
|
|
672
|
-
if (/\b(delete|remove)\b/.test(normalizedPrompt)) {
|
|
677
|
+
const getIntentToolChoice = ({ hasKnownCollection, hasKnownDocument, hasKnownGlobal, intent })=>{
|
|
678
|
+
if (intent === "create" && hasKnownCollection) {
|
|
673
679
|
return {
|
|
674
|
-
toolName: "
|
|
680
|
+
toolName: "proposeCreateDoc",
|
|
675
681
|
type: "tool"
|
|
676
682
|
};
|
|
677
683
|
}
|
|
678
|
-
|
|
679
|
-
if (/\b(update|edit|change|modify|einfügen|hinzufügen|addieren)\b/.test(normalizedPrompt)) {
|
|
684
|
+
if (intent === "update" && hasKnownDocument) {
|
|
680
685
|
return {
|
|
681
686
|
toolName: "proposeUpdateDoc",
|
|
682
687
|
type: "tool"
|
|
683
688
|
};
|
|
684
689
|
}
|
|
685
|
-
|
|
686
|
-
if (/\b(create|build|generate|write|draft|make|add|füge|einfügen|hinzufügen|addieren)\b/.test(normalizedPrompt)) {
|
|
690
|
+
if (intent === "delete" && hasKnownDocument) {
|
|
687
691
|
return {
|
|
688
|
-
toolName: "
|
|
692
|
+
toolName: "proposeDeleteDoc",
|
|
693
|
+
type: "tool"
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
if (intent === "updateGlobal" && hasKnownGlobal) {
|
|
697
|
+
return {
|
|
698
|
+
toolName: "proposeUpdateGlobal",
|
|
689
699
|
type: "tool"
|
|
690
700
|
};
|
|
691
701
|
}
|
|
692
702
|
return undefined;
|
|
693
703
|
};
|
|
704
|
+
const getToolNamesForIntent = ({ hasAttachments, hasKnownCollection, hasCurrentDocument, hasCurrentGlobal, intent })=>{
|
|
705
|
+
if (hasCurrentGlobal) {
|
|
706
|
+
return new Set(intent === "updateGlobal" ? [
|
|
707
|
+
"getGlobal",
|
|
708
|
+
"listGlobals",
|
|
709
|
+
"proposeUpdateGlobal"
|
|
710
|
+
] : [
|
|
711
|
+
"getGlobal",
|
|
712
|
+
"listGlobals"
|
|
713
|
+
]);
|
|
714
|
+
}
|
|
715
|
+
if (hasCurrentDocument) {
|
|
716
|
+
if (intent === "delete") return new Set([
|
|
717
|
+
"getDoc",
|
|
718
|
+
"proposeDeleteDoc"
|
|
719
|
+
]);
|
|
720
|
+
if (intent === "update" || intent === "create") return new Set([
|
|
721
|
+
"getDoc",
|
|
722
|
+
"listCollections",
|
|
723
|
+
"proposeUpdateDoc"
|
|
724
|
+
]);
|
|
725
|
+
return new Set([
|
|
726
|
+
"getDoc",
|
|
727
|
+
"listCollections"
|
|
728
|
+
]);
|
|
729
|
+
}
|
|
730
|
+
if (hasKnownCollection) {
|
|
731
|
+
if (intent === "create") return new Set([
|
|
732
|
+
"getDoc",
|
|
733
|
+
"listCollections",
|
|
734
|
+
"proposeCreateDoc",
|
|
735
|
+
"searchDocs"
|
|
736
|
+
]);
|
|
737
|
+
if (intent === "update") return new Set([
|
|
738
|
+
"getDoc",
|
|
739
|
+
"listCollections",
|
|
740
|
+
"proposeUpdateDoc",
|
|
741
|
+
"searchDocs"
|
|
742
|
+
]);
|
|
743
|
+
if (intent === "delete") return new Set([
|
|
744
|
+
"getDoc",
|
|
745
|
+
"proposeDeleteDoc",
|
|
746
|
+
"searchDocs"
|
|
747
|
+
]);
|
|
748
|
+
return new Set([
|
|
749
|
+
"getDoc",
|
|
750
|
+
"listCollections",
|
|
751
|
+
"searchDocs"
|
|
752
|
+
]);
|
|
753
|
+
}
|
|
754
|
+
const toolNames = intent === "create" ? new Set([
|
|
755
|
+
"getDoc",
|
|
756
|
+
"listCollections",
|
|
757
|
+
"proposeCreateDoc",
|
|
758
|
+
"searchDocs"
|
|
759
|
+
]) : intent === "update" ? new Set([
|
|
760
|
+
"getDoc",
|
|
761
|
+
"getGlobal",
|
|
762
|
+
"listCollections",
|
|
763
|
+
"listGlobals",
|
|
764
|
+
"proposeUpdateDoc",
|
|
765
|
+
"proposeUpdateGlobal",
|
|
766
|
+
"searchDocs"
|
|
767
|
+
]) : intent === "delete" ? new Set([
|
|
768
|
+
"getDoc",
|
|
769
|
+
"listCollections",
|
|
770
|
+
"proposeDeleteDoc",
|
|
771
|
+
"searchDocs"
|
|
772
|
+
]) : new Set([
|
|
773
|
+
"getDoc",
|
|
774
|
+
"getGlobal",
|
|
775
|
+
"listCollections",
|
|
776
|
+
"listGlobals",
|
|
777
|
+
"searchDocs"
|
|
778
|
+
]);
|
|
779
|
+
if (hasAttachments && (intent === "create" || intent === "update")) {
|
|
780
|
+
toolNames.add("proposeUpdateDoc");
|
|
781
|
+
}
|
|
782
|
+
return toolNames;
|
|
783
|
+
};
|
|
694
784
|
export const createChatHandler = (options = {})=>async (req)=>{
|
|
695
785
|
if (!req.user) return Response.json({
|
|
696
786
|
error: "Unauthorized"
|
|
@@ -819,6 +909,7 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
819
909
|
try {
|
|
820
910
|
const proposals = [];
|
|
821
911
|
const toolFailures = [];
|
|
912
|
+
const proposalRepairTracker = createProposalRepairTracker();
|
|
822
913
|
const registerToolFailure = (failure)=>{
|
|
823
914
|
toolFailures.push(failure);
|
|
824
915
|
logHandlerEvent(req, "warn", {
|
|
@@ -828,18 +919,66 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
828
919
|
promptPreview: getLogPreview(prompt)
|
|
829
920
|
});
|
|
830
921
|
};
|
|
831
|
-
const createToolError = ({ collection, details, message, slug, tool })=>{
|
|
922
|
+
const createToolError = ({ collection, details, errorCode = "NON_RETRYABLE_TOOL_ERROR", message, slug, tool })=>{
|
|
832
923
|
registerToolFailure({
|
|
833
924
|
collection,
|
|
834
925
|
details,
|
|
926
|
+
errorCode,
|
|
835
927
|
message,
|
|
928
|
+
retryable: false,
|
|
929
|
+
slug,
|
|
930
|
+
tool
|
|
931
|
+
});
|
|
932
|
+
return {
|
|
933
|
+
error: message,
|
|
934
|
+
errorCode,
|
|
935
|
+
retryable: false
|
|
936
|
+
};
|
|
937
|
+
};
|
|
938
|
+
const createRepairableToolError = ({ collection, details, id, issues, message, slug, tool })=>{
|
|
939
|
+
const repair = proposalRepairTracker.registerFailure({
|
|
940
|
+
collection,
|
|
941
|
+
id,
|
|
942
|
+
slug,
|
|
943
|
+
tool
|
|
944
|
+
});
|
|
945
|
+
const responseMessage = repair.retryable ? message : "The single proposal repair attempt failed. Do not call the same proposal tool for this target again.";
|
|
946
|
+
registerToolFailure({
|
|
947
|
+
collection,
|
|
948
|
+
details,
|
|
949
|
+
errorCode: repair.errorCode,
|
|
950
|
+
message: responseMessage,
|
|
951
|
+
retryable: repair.retryable,
|
|
836
952
|
slug,
|
|
837
953
|
tool
|
|
838
954
|
});
|
|
839
955
|
return {
|
|
840
|
-
error:
|
|
956
|
+
error: responseMessage,
|
|
957
|
+
errorCode: repair.errorCode,
|
|
958
|
+
repair: {
|
|
959
|
+
attempt: repair.attempt,
|
|
960
|
+
issues,
|
|
961
|
+
maxAttempts: repair.maxAttempts
|
|
962
|
+
},
|
|
963
|
+
retryable: repair.retryable
|
|
841
964
|
};
|
|
842
965
|
};
|
|
966
|
+
const getProposalRepairLimitError = ({ collection, id, slug, tool })=>{
|
|
967
|
+
const callState = proposalRepairTracker.beginCall({
|
|
968
|
+
collection,
|
|
969
|
+
id,
|
|
970
|
+
slug,
|
|
971
|
+
tool
|
|
972
|
+
});
|
|
973
|
+
if (callState !== "blocked") return null;
|
|
974
|
+
return createToolError({
|
|
975
|
+
collection,
|
|
976
|
+
errorCode: "REPAIR_EXHAUSTED",
|
|
977
|
+
message: "The single proposal repair attempt was already used. Do not call this proposal tool for the same target again.",
|
|
978
|
+
slug,
|
|
979
|
+
tool
|
|
980
|
+
});
|
|
981
|
+
};
|
|
843
982
|
const addSignedProposal = (proposal)=>{
|
|
844
983
|
if ("data" in proposal && proposal.data && containsSensitiveData(proposal.data)) {
|
|
845
984
|
return createToolError({
|
|
@@ -921,6 +1060,12 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
921
1060
|
parent: global.slug
|
|
922
1061
|
}))
|
|
923
1062
|
];
|
|
1063
|
+
const explicitlyMentionedDocumentKeys = new Set((body?.mentions || []).flatMap((mention)=>mention.type === "doc" && mention.collection && mention.id ? [
|
|
1064
|
+
`${mention.collection}:${mention.id}`
|
|
1065
|
+
] : []));
|
|
1066
|
+
const explicitlyMentionedGlobalSlugs = new Set((body?.mentions || []).flatMap((mention)=>mention.type === "global" && mention.slug ? [
|
|
1067
|
+
mention.slug
|
|
1068
|
+
] : []));
|
|
924
1069
|
const mentionContext = await getMentionContext({
|
|
925
1070
|
blockContexts,
|
|
926
1071
|
collectionSlugs,
|
|
@@ -930,37 +1075,65 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
930
1075
|
req
|
|
931
1076
|
});
|
|
932
1077
|
if (requestedCollectionSlug && requestedDocumentID) {
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1078
|
+
const currentDocumentKey = `${requestedCollectionSlug}:${requestedDocumentID}`;
|
|
1079
|
+
if (explicitlyMentionedDocumentKeys.has(currentDocumentKey)) {
|
|
1080
|
+
mentionContext.push({
|
|
1081
|
+
collection: requestedCollectionSlug,
|
|
1082
|
+
id: requestedDocumentID,
|
|
1083
|
+
source: "mention",
|
|
1084
|
+
type: "currentDocumentScope"
|
|
1085
|
+
});
|
|
1086
|
+
} else {
|
|
1087
|
+
const currentCollectionConfig = allowedCollectionsBySlug.get(requestedCollectionSlug);
|
|
1088
|
+
const fieldSelection = resolveToolFieldSelection({
|
|
1089
|
+
config: currentCollectionConfig
|
|
1090
|
+
});
|
|
1091
|
+
const currentDocument = await req.payload.findByID({
|
|
1092
|
+
collection: requestedCollectionSlug,
|
|
1093
|
+
depth: fieldSelection.depth,
|
|
1094
|
+
id: requestedDocumentID,
|
|
1095
|
+
...activeLocale ? {
|
|
1096
|
+
locale: activeLocale
|
|
1097
|
+
} : {},
|
|
1098
|
+
overrideAccess: false,
|
|
1099
|
+
req,
|
|
1100
|
+
select: fieldSelection.select
|
|
1101
|
+
});
|
|
1102
|
+
mentionContext.push({
|
|
1103
|
+
collection: requestedCollectionSlug,
|
|
1104
|
+
document: currentDocument,
|
|
1105
|
+
id: requestedDocumentID,
|
|
1106
|
+
type: "currentDocument"
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
949
1109
|
} else if (requestedGlobalSlug) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1110
|
+
if (explicitlyMentionedGlobalSlugs.has(requestedGlobalSlug)) {
|
|
1111
|
+
mentionContext.push({
|
|
1112
|
+
slug: requestedGlobalSlug,
|
|
1113
|
+
source: "mention",
|
|
1114
|
+
type: "currentGlobalScope"
|
|
1115
|
+
});
|
|
1116
|
+
} else {
|
|
1117
|
+
const currentGlobalConfig = globalConfigsBySlug.get(requestedGlobalSlug);
|
|
1118
|
+
const fieldSelection = resolveToolFieldSelection({
|
|
1119
|
+
config: currentGlobalConfig
|
|
1120
|
+
});
|
|
1121
|
+
const currentGlobal = await req.payload.findGlobal({
|
|
1122
|
+
depth: fieldSelection.depth,
|
|
1123
|
+
...activeLocale ? {
|
|
1124
|
+
locale: activeLocale
|
|
1125
|
+
} : {},
|
|
1126
|
+
overrideAccess: false,
|
|
1127
|
+
req,
|
|
1128
|
+
select: fieldSelection.select,
|
|
1129
|
+
slug: requestedGlobalSlug
|
|
1130
|
+
});
|
|
1131
|
+
mentionContext.push({
|
|
1132
|
+
global: currentGlobal,
|
|
1133
|
+
slug: requestedGlobalSlug,
|
|
1134
|
+
type: "currentGlobal"
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
964
1137
|
}
|
|
965
1138
|
const mediaAttachmentContext = await getMediaAttachmentContext({
|
|
966
1139
|
allowedCollectionsBySlug,
|
|
@@ -1008,7 +1181,17 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1008
1181
|
aliasMap: collectionAliasMap,
|
|
1009
1182
|
prompt
|
|
1010
1183
|
});
|
|
1011
|
-
const
|
|
1184
|
+
const chatIntent = getChatIntent({
|
|
1185
|
+
hasCurrentDocument: Boolean(requestedCollectionSlug && requestedDocumentID),
|
|
1186
|
+
hasCurrentGlobal: Boolean(requestedGlobalSlug),
|
|
1187
|
+
prompt
|
|
1188
|
+
});
|
|
1189
|
+
const writeIntent = [
|
|
1190
|
+
"create",
|
|
1191
|
+
"delete",
|
|
1192
|
+
"update",
|
|
1193
|
+
"updateGlobal"
|
|
1194
|
+
].includes(chatIntent);
|
|
1012
1195
|
const inferredCollectionSlug = requestedCollectionSlug || (mentionedCollectionSlugs.length === 1 ? mentionedCollectionSlugs[0] : mentionedCollectionSlugs.length === 0 && likelyCollectionMatches.length === 1 ? likelyCollectionMatches[0] : undefined);
|
|
1013
1196
|
const inferredCollectionConfig = inferredCollectionSlug ? allowedCollectionsBySlug.get(inferredCollectionSlug) : undefined;
|
|
1014
1197
|
if (inferredCollectionConfig && !mentionContext.some((item)=>item.type === "collection" && item.slug === inferredCollectionConfig.slug)) {
|
|
@@ -1030,10 +1213,16 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1030
1213
|
}));
|
|
1031
1214
|
}
|
|
1032
1215
|
}
|
|
1033
|
-
let intentToolChoice =
|
|
1216
|
+
let intentToolChoice = getIntentToolChoice({
|
|
1217
|
+
hasKnownCollection: Boolean(inferredCollectionConfig),
|
|
1218
|
+
hasKnownDocument: Boolean(requestedCollectionSlug && requestedDocumentID),
|
|
1219
|
+
hasKnownGlobal: Boolean(requestedGlobalSlug),
|
|
1220
|
+
intent: chatIntent
|
|
1221
|
+
});
|
|
1034
1222
|
logHandlerEvent(req, "info", {
|
|
1035
1223
|
activeLocale,
|
|
1036
1224
|
allowedCollectionCount: allowedCollections.length,
|
|
1225
|
+
chatIntent,
|
|
1037
1226
|
collectionSlugs,
|
|
1038
1227
|
focusedCollections: mentionedCollectionSlugs,
|
|
1039
1228
|
globalSlugs,
|
|
@@ -1045,6 +1234,19 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1045
1234
|
writeIntent
|
|
1046
1235
|
});
|
|
1047
1236
|
const collectionSlugSchema = collectionSlugs.length > 0 ? z.enum(collectionSlugs) : z.string().refine(()=>false, "No collection is in scope.");
|
|
1237
|
+
const focusedCollectionDataSchema = createPayloadDataSchema(inferredCollectionConfig);
|
|
1238
|
+
const focusedCollectionSlugSchema = inferredCollectionSlug ? z.literal(inferredCollectionSlug) : collectionSlugSchema;
|
|
1239
|
+
const focusedGlobalConfig = requestedGlobalSlug ? globalConfigsBySlug.get(requestedGlobalSlug) : undefined;
|
|
1240
|
+
const focusedGlobalDataSchema = createPayloadDataSchema(focusedGlobalConfig);
|
|
1241
|
+
const collectionReadFieldsSchema = createToolFieldNamesSchema(allowedCollections);
|
|
1242
|
+
const globalReadFieldsSchema = createToolFieldNamesSchema(globalConfigs);
|
|
1243
|
+
const createDataSchema = inferredCollectionConfig ? focusedCollectionDataSchema : genericPayloadDataSchema;
|
|
1244
|
+
const createLocalizedDataSchema = createLocalizedPayloadDataSchema(createDataSchema);
|
|
1245
|
+
const updateHasMultipleTargets = mediaAttachmentContext.length > 0 && !requestedDocumentID;
|
|
1246
|
+
const updateDataSchema = inferredCollectionConfig && !updateHasMultipleTargets ? focusedCollectionDataSchema : genericPayloadDataSchema;
|
|
1247
|
+
const updateLocalizedDataSchema = createLocalizedPayloadDataSchema(updateDataSchema);
|
|
1248
|
+
const globalDataSchema = focusedGlobalConfig ? focusedGlobalDataSchema : genericPayloadDataSchema;
|
|
1249
|
+
const globalLocalizedDataSchema = createLocalizedPayloadDataSchema(globalDataSchema);
|
|
1048
1250
|
const getDisallowedCollectionActionError = (collection, action)=>{
|
|
1049
1251
|
if (isCollectionActionAllowed({
|
|
1050
1252
|
action,
|
|
@@ -1060,12 +1262,13 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1060
1262
|
};
|
|
1061
1263
|
const allTools = {
|
|
1062
1264
|
getDoc: {
|
|
1063
|
-
description: "Read a document by collection and id.",
|
|
1265
|
+
description: "Read a compact document by collection and id. Optionally request up to 12 exact top-level schema fields. Explicitly @-mentioned documents remain complete.",
|
|
1064
1266
|
inputSchema: z.object({
|
|
1065
1267
|
collection: collectionSlugSchema,
|
|
1268
|
+
fields: collectionReadFieldsSchema,
|
|
1066
1269
|
id: z.string().min(1)
|
|
1067
1270
|
}),
|
|
1068
|
-
execute: async ({ collection, id })=>{
|
|
1271
|
+
execute: async ({ collection, fields, id })=>{
|
|
1069
1272
|
if (requestedDocumentScope && (collection !== requestedCollectionSlug || id !== requestedDocumentID)) {
|
|
1070
1273
|
return createToolError({
|
|
1071
1274
|
collection,
|
|
@@ -1073,16 +1276,37 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1073
1276
|
tool: "getDoc"
|
|
1074
1277
|
});
|
|
1075
1278
|
}
|
|
1076
|
-
|
|
1279
|
+
const explicitlyMentioned = explicitlyMentionedDocumentKeys.has(`${collection}:${id}`);
|
|
1280
|
+
const collectionConfig = allowedCollectionsBySlug.get(collection);
|
|
1281
|
+
const fieldSelection = resolveToolFieldSelection({
|
|
1282
|
+
config: collectionConfig,
|
|
1283
|
+
requestedFields: fields
|
|
1284
|
+
});
|
|
1285
|
+
if (!explicitlyMentioned && fieldSelection.invalidFields.length > 0) {
|
|
1286
|
+
return createToolError({
|
|
1287
|
+
collection,
|
|
1288
|
+
details: {
|
|
1289
|
+
invalidFields: fieldSelection.invalidFields
|
|
1290
|
+
},
|
|
1291
|
+
errorCode: "INVALID_FIELD_SELECTION",
|
|
1292
|
+
message: `Unknown or sensitive fields requested for ${collection}: ${fieldSelection.invalidFields.join(", ")}.`,
|
|
1293
|
+
tool: "getDoc"
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
const document = await req.payload.findByID({
|
|
1077
1297
|
collection: collection,
|
|
1078
|
-
depth: 2,
|
|
1298
|
+
depth: explicitlyMentioned ? 2 : fieldSelection.depth,
|
|
1079
1299
|
id,
|
|
1080
1300
|
...activeLocale ? {
|
|
1081
1301
|
locale: activeLocale
|
|
1082
1302
|
} : {},
|
|
1083
1303
|
overrideAccess: false,
|
|
1084
|
-
req
|
|
1304
|
+
req,
|
|
1305
|
+
...!explicitlyMentioned ? {
|
|
1306
|
+
select: fieldSelection.select
|
|
1307
|
+
} : {}
|
|
1085
1308
|
});
|
|
1309
|
+
return redactSensitiveData(document);
|
|
1086
1310
|
}
|
|
1087
1311
|
},
|
|
1088
1312
|
listCollections: {
|
|
@@ -1114,11 +1338,12 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1114
1338
|
}
|
|
1115
1339
|
},
|
|
1116
1340
|
getGlobal: {
|
|
1117
|
-
description: "Read a global
|
|
1341
|
+
description: "Read a compact global. Optionally request up to 12 exact top-level schema fields. Explicitly @-mentioned globals remain complete.",
|
|
1118
1342
|
inputSchema: z.object({
|
|
1343
|
+
fields: globalReadFieldsSchema,
|
|
1119
1344
|
slug: z.string().min(1)
|
|
1120
1345
|
}),
|
|
1121
|
-
execute: async ({ slug })=>{
|
|
1346
|
+
execute: async ({ fields, slug })=>{
|
|
1122
1347
|
if (requestedDocumentScope && slug !== requestedGlobalSlug) {
|
|
1123
1348
|
return createToolError({
|
|
1124
1349
|
message: "Only the current global can be read in this context.",
|
|
@@ -1134,15 +1359,35 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1134
1359
|
tool: "getGlobal"
|
|
1135
1360
|
});
|
|
1136
1361
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1362
|
+
const explicitlyMentioned = explicitlyMentionedGlobalSlugs.has(slug);
|
|
1363
|
+
const fieldSelection = resolveToolFieldSelection({
|
|
1364
|
+
config: globalConfig,
|
|
1365
|
+
requestedFields: fields
|
|
1366
|
+
});
|
|
1367
|
+
if (!explicitlyMentioned && fieldSelection.invalidFields.length > 0) {
|
|
1368
|
+
return createToolError({
|
|
1369
|
+
details: {
|
|
1370
|
+
invalidFields: fieldSelection.invalidFields
|
|
1371
|
+
},
|
|
1372
|
+
errorCode: "INVALID_FIELD_SELECTION",
|
|
1373
|
+
message: `Unknown or sensitive fields requested for global ${slug}: ${fieldSelection.invalidFields.join(", ")}.`,
|
|
1374
|
+
slug,
|
|
1375
|
+
tool: "getGlobal"
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
const global = await req.payload.findGlobal({
|
|
1379
|
+
depth: explicitlyMentioned ? 2 : fieldSelection.depth,
|
|
1139
1380
|
...activeLocale ? {
|
|
1140
1381
|
locale: activeLocale
|
|
1141
1382
|
} : {},
|
|
1142
1383
|
overrideAccess: false,
|
|
1143
1384
|
req,
|
|
1385
|
+
...!explicitlyMentioned ? {
|
|
1386
|
+
select: fieldSelection.select
|
|
1387
|
+
} : {},
|
|
1144
1388
|
slug: slug
|
|
1145
1389
|
});
|
|
1390
|
+
return redactSensitiveData(global);
|
|
1146
1391
|
}
|
|
1147
1392
|
},
|
|
1148
1393
|
listGlobals: {
|
|
@@ -1174,16 +1419,23 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1174
1419
|
proposeCreateDoc: {
|
|
1175
1420
|
description: "Propose document creation. Use exact schema fields; include required fields. Use localizedData for multi-locale writes.",
|
|
1176
1421
|
inputSchema: z.object({
|
|
1177
|
-
collection:
|
|
1178
|
-
data:
|
|
1422
|
+
collection: focusedCollectionSlugSchema,
|
|
1423
|
+
data: createDataSchema.optional(),
|
|
1179
1424
|
label: z.string().min(1),
|
|
1180
|
-
localizedData:
|
|
1425
|
+
localizedData: createLocalizedDataSchema.optional()
|
|
1181
1426
|
}).refine((value)=>Boolean(value.data || value.localizedData), {
|
|
1182
1427
|
message: "Either data or localizedData is required."
|
|
1183
1428
|
}),
|
|
1184
1429
|
execute: async ({ collection, data, label, localizedData })=>{
|
|
1185
1430
|
const permissionError = getDisallowedCollectionActionError(collection, "create");
|
|
1186
1431
|
if (permissionError) return permissionError;
|
|
1432
|
+
const repairTargetID = getSafeProposalLabel(label);
|
|
1433
|
+
const repairLimitError = getProposalRepairLimitError({
|
|
1434
|
+
collection,
|
|
1435
|
+
id: repairTargetID,
|
|
1436
|
+
tool: "proposeCreateDoc"
|
|
1437
|
+
});
|
|
1438
|
+
if (repairLimitError) return repairLimitError;
|
|
1187
1439
|
const collectionConfig = allowedCollectionsBySlug.get(collection);
|
|
1188
1440
|
const collectionFields = collectionConfig?.fields || [];
|
|
1189
1441
|
const preparedData = prepareProposalWriteData({
|
|
@@ -1197,13 +1449,15 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1197
1449
|
if (preparedData.issues.length > 0) {
|
|
1198
1450
|
const titleFieldName = titleFieldByCollection[collection];
|
|
1199
1451
|
const missingTitleField = titleFieldName ? preparedData.issues.some((issue)=>issue.path === titleFieldName || issue.path.endsWith(`.${titleFieldName}`)) : false;
|
|
1200
|
-
return
|
|
1452
|
+
return createRepairableToolError({
|
|
1201
1453
|
collection,
|
|
1202
1454
|
details: {
|
|
1203
1455
|
issues: preparedData.issues,
|
|
1204
1456
|
titleFieldName
|
|
1205
1457
|
},
|
|
1206
|
-
|
|
1458
|
+
id: repairTargetID,
|
|
1459
|
+
issues: compactProposalRepairIssues(preparedData.issues),
|
|
1460
|
+
message: missingTitleField ? `Create proposal is missing the required title field "${titleFieldName}" for ${collection}.` : `Create proposal data for ${collection} is invalid. Correct only repair.issues and call the same tool once more.`,
|
|
1207
1461
|
tool: "proposeCreateDoc"
|
|
1208
1462
|
});
|
|
1209
1463
|
}
|
|
@@ -1234,13 +1488,19 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1234
1488
|
}
|
|
1235
1489
|
const missingBlockTypes = requestedBlockTypes.filter((blockType)=>!proposalBlockTypes.has(blockType));
|
|
1236
1490
|
if (missingBlockTypes.length > 0) {
|
|
1237
|
-
return
|
|
1491
|
+
return createRepairableToolError({
|
|
1238
1492
|
collection,
|
|
1239
1493
|
details: {
|
|
1240
1494
|
missingBlockTypes,
|
|
1241
1495
|
requestedBlockTypes
|
|
1242
1496
|
},
|
|
1243
|
-
|
|
1497
|
+
id: repairTargetID,
|
|
1498
|
+
issues: missingBlockTypes.map((blockType)=>({
|
|
1499
|
+
code: "missing_requested_block",
|
|
1500
|
+
hint: `Add a complete "${blockType}" block using the exact blockType and schema fields.`,
|
|
1501
|
+
path: "blocks"
|
|
1502
|
+
})),
|
|
1503
|
+
message: `Create proposal for ${collection} is missing requested block types: ${missingBlockTypes.join(", ")}.`,
|
|
1244
1504
|
tool: "proposeCreateDoc"
|
|
1245
1505
|
});
|
|
1246
1506
|
}
|
|
@@ -1344,11 +1604,11 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1344
1604
|
proposeUpdateDoc: {
|
|
1345
1605
|
description: "Propose document update. Use exact schema fields. Use localizedData for multi-locale writes.",
|
|
1346
1606
|
inputSchema: z.object({
|
|
1347
|
-
collection: collectionSlugSchema,
|
|
1348
|
-
data:
|
|
1607
|
+
collection: updateHasMultipleTargets ? collectionSlugSchema : focusedCollectionSlugSchema,
|
|
1608
|
+
data: updateDataSchema.optional(),
|
|
1349
1609
|
id: z.string().min(1),
|
|
1350
1610
|
label: z.string().min(1),
|
|
1351
|
-
localizedData:
|
|
1611
|
+
localizedData: updateLocalizedDataSchema.optional()
|
|
1352
1612
|
}).refine((value)=>Boolean(value.data || value.localizedData), {
|
|
1353
1613
|
message: "Either data or localizedData is required."
|
|
1354
1614
|
}),
|
|
@@ -1362,6 +1622,12 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1362
1622
|
}
|
|
1363
1623
|
const permissionError = getDisallowedCollectionActionError(collection, "update");
|
|
1364
1624
|
if (permissionError) return permissionError;
|
|
1625
|
+
const repairLimitError = getProposalRepairLimitError({
|
|
1626
|
+
collection,
|
|
1627
|
+
id,
|
|
1628
|
+
tool: "proposeUpdateDoc"
|
|
1629
|
+
});
|
|
1630
|
+
if (repairLimitError) return repairLimitError;
|
|
1365
1631
|
const collectionConfig = allowedCollectionsBySlug.get(collection);
|
|
1366
1632
|
const collectionFields = collectionConfig?.fields || [];
|
|
1367
1633
|
const preparedData = prepareProposalWriteData({
|
|
@@ -1373,12 +1639,14 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1373
1639
|
mode: "update"
|
|
1374
1640
|
});
|
|
1375
1641
|
if (preparedData.issues.length > 0) {
|
|
1376
|
-
return
|
|
1642
|
+
return createRepairableToolError({
|
|
1377
1643
|
collection,
|
|
1378
1644
|
details: {
|
|
1379
1645
|
issues: preparedData.issues
|
|
1380
1646
|
},
|
|
1381
|
-
|
|
1647
|
+
id,
|
|
1648
|
+
issues: compactProposalRepairIssues(preparedData.issues),
|
|
1649
|
+
message: `Update proposal data for ${collection}:${id} is invalid. Correct only repair.issues and call the same tool once more.`,
|
|
1382
1650
|
tool: "proposeUpdateDoc"
|
|
1383
1651
|
});
|
|
1384
1652
|
}
|
|
@@ -1449,10 +1717,10 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1449
1717
|
proposeUpdateGlobal: {
|
|
1450
1718
|
description: "Propose global update. Use localizedData for multi-locale writes.",
|
|
1451
1719
|
inputSchema: z.object({
|
|
1452
|
-
data:
|
|
1720
|
+
data: globalDataSchema.optional(),
|
|
1453
1721
|
label: z.string().min(1),
|
|
1454
|
-
localizedData:
|
|
1455
|
-
slug: z.string().min(1)
|
|
1722
|
+
localizedData: globalLocalizedDataSchema.optional(),
|
|
1723
|
+
slug: requestedGlobalSlug ? z.literal(requestedGlobalSlug) : z.string().min(1)
|
|
1456
1724
|
}).refine((value)=>Boolean(value.data || value.localizedData), {
|
|
1457
1725
|
message: "Either data or localizedData is required."
|
|
1458
1726
|
}),
|
|
@@ -1472,6 +1740,11 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1472
1740
|
tool: "proposeUpdateGlobal"
|
|
1473
1741
|
});
|
|
1474
1742
|
}
|
|
1743
|
+
const repairLimitError = getProposalRepairLimitError({
|
|
1744
|
+
slug,
|
|
1745
|
+
tool: "proposeUpdateGlobal"
|
|
1746
|
+
});
|
|
1747
|
+
if (repairLimitError) return repairLimitError;
|
|
1475
1748
|
const preparedData = prepareProposalWriteData({
|
|
1476
1749
|
collectionConfig: {
|
|
1477
1750
|
fields: globalConfig.fields || [],
|
|
@@ -1484,11 +1757,12 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1484
1757
|
mode: "update"
|
|
1485
1758
|
});
|
|
1486
1759
|
if (preparedData.issues.length > 0) {
|
|
1487
|
-
return
|
|
1760
|
+
return createRepairableToolError({
|
|
1488
1761
|
details: {
|
|
1489
1762
|
issues: preparedData.issues
|
|
1490
1763
|
},
|
|
1491
|
-
|
|
1764
|
+
issues: compactProposalRepairIssues(preparedData.issues),
|
|
1765
|
+
message: `Update proposal data for global ${slug} is invalid. Correct only repair.issues and call the same tool once more.`,
|
|
1492
1766
|
slug,
|
|
1493
1767
|
tool: "proposeUpdateGlobal"
|
|
1494
1768
|
});
|
|
@@ -1510,20 +1784,37 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1510
1784
|
}
|
|
1511
1785
|
},
|
|
1512
1786
|
searchDocs: {
|
|
1513
|
-
description: "Search documents in one collection.",
|
|
1787
|
+
description: "Search compact documents in one collection. Returns default identity fields or up to 12 requested exact top-level schema fields.",
|
|
1514
1788
|
inputSchema: z.object({
|
|
1515
1789
|
collection: collectionSlugSchema,
|
|
1790
|
+
fields: collectionReadFieldsSchema,
|
|
1516
1791
|
limit: z.number().int().min(1).max(10).default(5),
|
|
1517
1792
|
query: z.string().optional()
|
|
1518
1793
|
}),
|
|
1519
|
-
execute: async ({ collection, limit, query })=>{
|
|
1794
|
+
execute: async ({ collection, fields, limit, query })=>{
|
|
1520
1795
|
const collectionConfig = allowedCollectionsBySlug.get(collection);
|
|
1796
|
+
const fieldSelection = resolveToolFieldSelection({
|
|
1797
|
+
config: collectionConfig,
|
|
1798
|
+
mode: "search",
|
|
1799
|
+
requestedFields: fields
|
|
1800
|
+
});
|
|
1801
|
+
if (fieldSelection.invalidFields.length > 0) {
|
|
1802
|
+
return createToolError({
|
|
1803
|
+
collection,
|
|
1804
|
+
details: {
|
|
1805
|
+
invalidFields: fieldSelection.invalidFields
|
|
1806
|
+
},
|
|
1807
|
+
errorCode: "INVALID_FIELD_SELECTION",
|
|
1808
|
+
message: `Unknown or sensitive fields requested for ${collection}: ${fieldSelection.invalidFields.join(", ")}.`,
|
|
1809
|
+
tool: "searchDocs"
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1521
1812
|
const searchableFields = collectionConfig?.fields.flatMap((field)=>{
|
|
1522
1813
|
if (!("name" in field) || ![
|
|
1523
1814
|
"email",
|
|
1524
1815
|
"text",
|
|
1525
1816
|
"textarea"
|
|
1526
|
-
].includes(field.type) || !field.name) return [];
|
|
1817
|
+
].includes(field.type) || !field.name || isSensitiveKey(field.name)) return [];
|
|
1527
1818
|
return [
|
|
1528
1819
|
field.name
|
|
1529
1820
|
];
|
|
@@ -1535,34 +1826,35 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1535
1826
|
}
|
|
1536
1827
|
}))
|
|
1537
1828
|
} : undefined;
|
|
1538
|
-
|
|
1829
|
+
const result = await req.payload.find({
|
|
1539
1830
|
collection: collection,
|
|
1540
|
-
depth:
|
|
1831
|
+
depth: fieldSelection.depth,
|
|
1541
1832
|
limit,
|
|
1542
1833
|
...activeLocale ? {
|
|
1543
1834
|
locale: activeLocale
|
|
1544
1835
|
} : {},
|
|
1545
1836
|
overrideAccess: false,
|
|
1546
1837
|
req,
|
|
1838
|
+
select: fieldSelection.select,
|
|
1547
1839
|
where
|
|
1548
1840
|
});
|
|
1841
|
+
return redactSensitiveData({
|
|
1842
|
+
docs: result.docs,
|
|
1843
|
+
hasNextPage: result.hasNextPage
|
|
1844
|
+
});
|
|
1549
1845
|
}
|
|
1550
1846
|
}
|
|
1551
1847
|
};
|
|
1552
|
-
const scopedToolNames =
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
"proposeUpdateGlobal"
|
|
1563
|
-
]) : null;
|
|
1564
|
-
const tools = scopedToolNames ? Object.fromEntries(Object.entries(allTools).filter(([name])=>scopedToolNames.has(name))) : allTools;
|
|
1565
|
-
if (intentToolChoice && scopedToolNames && !scopedToolNames.has(intentToolChoice.toolName)) {
|
|
1848
|
+
const scopedToolNames = getToolNamesForIntent({
|
|
1849
|
+
hasAttachments: Boolean(body?.attachments?.length),
|
|
1850
|
+
hasKnownCollection: Boolean(inferredCollectionConfig),
|
|
1851
|
+
hasCurrentDocument: Boolean(requestedCollectionSlug && requestedDocumentID),
|
|
1852
|
+
hasCurrentGlobal: Boolean(requestedGlobalSlug),
|
|
1853
|
+
intent: chatIntent
|
|
1854
|
+
});
|
|
1855
|
+
const tools = Object.fromEntries(Object.entries(allTools).filter(([name])=>scopedToolNames.has(name)));
|
|
1856
|
+
debug.tools = Object.keys(tools);
|
|
1857
|
+
if (intentToolChoice && !scopedToolNames.has(intentToolChoice.toolName)) {
|
|
1566
1858
|
intentToolChoice = undefined;
|
|
1567
1859
|
}
|
|
1568
1860
|
const encoder = new TextEncoder();
|
|
@@ -1577,34 +1869,68 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
1577
1869
|
model: providerConfig.modelID,
|
|
1578
1870
|
provider
|
|
1579
1871
|
});
|
|
1872
|
+
const promptCaching = options.promptCaching !== false;
|
|
1873
|
+
const explicitPromptCaching = promptCaching && !managedProvider?.baseURL;
|
|
1874
|
+
const sanitizedMentionContext = redactSensitiveData(mentionContext);
|
|
1875
|
+
const { cacheable: cacheableMentionContext, dynamic: dynamicMentionContext } = splitPromptCacheContext(sanitizedMentionContext);
|
|
1876
|
+
const staticSystemInstructions = [
|
|
1877
|
+
"You are a Payload CMS assistant. Inspect schema/content with tools before proposing writes.",
|
|
1878
|
+
"Mentions define the active CMS scope. Locale mentions define active locale; multiple locales require localizedData keyed by locale.",
|
|
1879
|
+
"Writes are proposals only. Never claim changes were applied before user confirmation.",
|
|
1880
|
+
"Uploaded media attachments appear in context as mediaAttachment entries. Use their exact IDs for upload fields.",
|
|
1881
|
+
"If an uploaded media document has editable descriptive fields, propose an update to that media document with suitable values.",
|
|
1882
|
+
"If a collection has a required `slug` field and the user does not provide it, generate a URL-friendly slug from the title or from the first meaningful word of the prompt.",
|
|
1883
|
+
"For create/update/delete requests, you must use proposal tools. Do not end with plain text if the user asked for a content change.",
|
|
1884
|
+
"If the user asks to create, update, refine, translate, remove, or delete content, produce at least one proposal tool call unless blocked by missing schema information or permissions.",
|
|
1885
|
+
"Put concrete field values only in tool data, not visible text.",
|
|
1886
|
+
"For blocks fields: use exact blockType values from schema, exact field names, and complete objects for required block fields.",
|
|
1887
|
+
"For arrays: every item must be an object matching the child field schema, not free text.",
|
|
1888
|
+
"If schema details are missing, call listCollections/listGlobals with a slug before proposing. If an inferred collection schema is already present in context, use it directly.",
|
|
1889
|
+
"Read tools return compact identity fields by default. Request only the additional fields needed for the current task; full schemas remain available separately.",
|
|
1890
|
+
"If a proposal tool returns retryable=true, correct only repair.issues and call the same proposal tool once more. If retryable=false, do not retry that tool target."
|
|
1891
|
+
];
|
|
1892
|
+
const cacheableSystemInstructions = [
|
|
1893
|
+
`Collection aliases: ${JSON.stringify(collectionAliasMap)}.`,
|
|
1894
|
+
`Focused required create fields: ${JSON.stringify(focusedRequiredFieldsByCollection)}.`,
|
|
1895
|
+
`Focused title fields: ${JSON.stringify(focusedTitleFieldByCollection)}. Infer concise titles when needed.`
|
|
1896
|
+
];
|
|
1897
|
+
const dynamicSystemInstructions = [
|
|
1898
|
+
`Likely collection matches for this prompt: ${JSON.stringify(likelyCollectionMatches)}.`,
|
|
1899
|
+
`Request intent: ${chatIntent}. Preferred proposal tool: ${intentToolChoice?.toolName || "none"}.`,
|
|
1900
|
+
"Visible response: plain text, under 40 words, no Markdown, no proposed content."
|
|
1901
|
+
];
|
|
1902
|
+
const system = createCachedSystemMessages({
|
|
1903
|
+
cacheableContext: cacheableMentionContext,
|
|
1904
|
+
cacheableInstructions: cacheableSystemInstructions,
|
|
1905
|
+
dynamicInstructions: dynamicSystemInstructions,
|
|
1906
|
+
enabled: explicitPromptCaching,
|
|
1907
|
+
provider,
|
|
1908
|
+
staticInstructions: staticSystemInstructions
|
|
1909
|
+
});
|
|
1910
|
+
const promptCacheProviderOptions = createPromptCacheProviderOptions({
|
|
1911
|
+
cacheKeyParts: [
|
|
1912
|
+
cacheableMentionContext,
|
|
1913
|
+
cacheableSystemInstructions,
|
|
1914
|
+
[
|
|
1915
|
+
...scopedToolNames
|
|
1916
|
+
].sort()
|
|
1917
|
+
],
|
|
1918
|
+
enabled: explicitPromptCaching,
|
|
1919
|
+
model: providerConfig.modelID,
|
|
1920
|
+
provider
|
|
1921
|
+
});
|
|
1580
1922
|
const result = streamText({
|
|
1581
1923
|
maxOutputTokens: options.maxOutputTokens || 700,
|
|
1582
1924
|
model,
|
|
1583
1925
|
prompt: buildPromptWithMentionContext({
|
|
1584
|
-
mentionContext,
|
|
1926
|
+
mentionContext: dynamicMentionContext,
|
|
1585
1927
|
prompt
|
|
1586
1928
|
}),
|
|
1929
|
+
...promptCacheProviderOptions ? {
|
|
1930
|
+
providerOptions: promptCacheProviderOptions
|
|
1931
|
+
} : {},
|
|
1587
1932
|
stopWhen: stepCountIs(6),
|
|
1588
|
-
system
|
|
1589
|
-
"You are a Payload CMS assistant. Inspect schema/content with tools before proposing writes.",
|
|
1590
|
-
"Mentions define the active CMS scope. Locale mentions define active locale; multiple locales require localizedData keyed by locale.",
|
|
1591
|
-
"Writes are proposals only. Never claim changes were applied before user confirmation.",
|
|
1592
|
-
"Uploaded media attachments appear in context as mediaAttachment entries. Use their exact IDs for upload fields.",
|
|
1593
|
-
"If an uploaded media document has editable descriptive fields, propose an update to that media document with suitable values.",
|
|
1594
|
-
"If a collection has a required `slug` field and the user does not provide it, generate a URL‑friendly slug from the title or from the first meaningful word of the prompt.",
|
|
1595
|
-
"For create/update/delete requests, you must use proposal tools. Do not end with plain text if the user asked for a content change.",
|
|
1596
|
-
"If the user asks to create, update, refine, translate, remove, or delete content, produce at least one proposal tool call unless blocked by missing schema information or permissions.",
|
|
1597
|
-
"Put concrete field values only in tool data, not visible text.",
|
|
1598
|
-
"For blocks fields: use exact blockType values from schema, exact field names, and complete objects for required block fields.",
|
|
1599
|
-
"For arrays: every item must be an object matching the child field schema, not free text.",
|
|
1600
|
-
"If schema details are missing, call listCollections/listGlobals with a slug before proposing. If an inferred collection schema is already present in context, use it directly.",
|
|
1601
|
-
`Collection aliases: ${JSON.stringify(collectionAliasMap)}.`,
|
|
1602
|
-
`Likely collection matches for this prompt: ${JSON.stringify(likelyCollectionMatches)}.`,
|
|
1603
|
-
`Focused required create fields: ${JSON.stringify(focusedRequiredFieldsByCollection)}.`,
|
|
1604
|
-
`Focused title fields: ${JSON.stringify(focusedTitleFieldByCollection)}. Infer concise titles when needed.`,
|
|
1605
|
-
`Preferred proposal tool for this prompt: ${intentToolChoice?.toolName || "none"}.`,
|
|
1606
|
-
"Visible response: plain text, under 40 words, no Markdown, no proposed content."
|
|
1607
|
-
].join("\n"),
|
|
1933
|
+
system,
|
|
1608
1934
|
...intentToolChoice ? {
|
|
1609
1935
|
toolChoice: intentToolChoice
|
|
1610
1936
|
} : {},
|