@mvriu5/payload-ai 1.5.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.
@@ -0,0 +1,14 @@
1
+ import type { PayloadHandler } from "payload";
2
+ import { type AIModelConfig, type ResolvedAIProviderConfig } from "../ai/providerOptions.js";
3
+ import { type ResolvedMaxTokenUsageOptions } from "../ai/tokenUsage.js";
4
+ import { type TranslationPageContext } from "../payload/documentTranslation.js";
5
+ type TranslateDocumentOptions = {
6
+ allowUserApiKeys?: boolean;
7
+ maxOutputTokens?: number;
8
+ maxTokenUsage?: ResolvedMaxTokenUsageOptions;
9
+ models?: AIModelConfig;
10
+ pageContexts: Map<string, TranslationPageContext>;
11
+ providers?: ResolvedAIProviderConfig[];
12
+ };
13
+ export declare const createTranslateDocumentHandler: (options: TranslateDocumentOptions) => PayloadHandler;
14
+ export {};
@@ -0,0 +1,186 @@
1
+ import { generateText } from "ai";
2
+ import { isAIProvider } from "../ai/providerOptions.js";
3
+ import { getModel, getProviderConfig } from "../ai/providerRuntime.js";
4
+ import { getExceededTokenUsageLimit, recordTokenUsage } from "../ai/tokenUsage.js";
5
+ import { getTranslationValues } from "../payload/documentTranslation.js";
6
+ const getDefaultLocale = (req)=>{
7
+ const localization = req.payload.config.localization;
8
+ if (!localization) return null;
9
+ return localization.defaultLocale || null;
10
+ };
11
+ const loadDocument = async ({ context, id, locale, req })=>{
12
+ if (context.type === "collection") {
13
+ if (id === undefined || id === null || id === "") return null;
14
+ return req.payload.findByID({
15
+ collection: context.slug,
16
+ depth: 0,
17
+ fallbackLocale: false,
18
+ id,
19
+ locale: locale,
20
+ overrideAccess: false,
21
+ req
22
+ });
23
+ }
24
+ return req.payload.findGlobal({
25
+ depth: 0,
26
+ fallbackLocale: false,
27
+ locale: locale,
28
+ overrideAccess: false,
29
+ req,
30
+ slug: context.slug
31
+ });
32
+ };
33
+ const parseTranslations = (text, sourceCount)=>{
34
+ const value = text.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
35
+ const parsed = JSON.parse(value);
36
+ if (!parsed.translations || typeof parsed.translations !== "object") throw new Error("Invalid translation response.");
37
+ return Array.from({
38
+ length: sourceCount
39
+ }, (_, index)=>parsed.translations?.[String(index)]);
40
+ };
41
+ export const createTranslateDocumentHandler = (options)=>async (req)=>{
42
+ if (!req.user) return Response.json({
43
+ error: "Unauthorized"
44
+ }, {
45
+ status: 401
46
+ });
47
+ const body = req.json ? await req.json().catch(()=>null) : null;
48
+ const locale = body?.locale?.trim();
49
+ const scopeSlug = body?.scope?.slug?.trim();
50
+ const scopeType = body?.scope?.type;
51
+ if (!body?.action || !locale || !scopeSlug || !scopeType) {
52
+ return Response.json({
53
+ error: "Action, locale, and page scope are required."
54
+ }, {
55
+ status: 400
56
+ });
57
+ }
58
+ const pageContext = options.pageContexts.get(`${scopeType}:${scopeSlug}`);
59
+ const defaultLocale = getDefaultLocale(req);
60
+ if (!pageContext || !defaultLocale || locale === defaultLocale) {
61
+ return Response.json({
62
+ available: false
63
+ });
64
+ }
65
+ try {
66
+ // Payload derives locale state on the request, so reads sharing one req must not run concurrently.
67
+ const sourceDocument = await loadDocument({
68
+ context: pageContext,
69
+ id: body.id,
70
+ locale: defaultLocale,
71
+ req
72
+ });
73
+ const targetDocument = await loadDocument({
74
+ context: pageContext,
75
+ id: body.id,
76
+ locale,
77
+ req
78
+ });
79
+ const sourceValues = getTranslationValues(pageContext.fields, sourceDocument);
80
+ const targetValues = getTranslationValues(pageContext.fields, targetDocument);
81
+ const populatedTargetPaths = new Set(targetValues.map((entry)=>entry.path));
82
+ const missingSourceValues = sourceValues.filter((entry)=>!populatedTargetPaths.has(entry.path));
83
+ const available = missingSourceValues.length > 0;
84
+ if (body.action === "status" || !available) return Response.json({
85
+ available
86
+ });
87
+ const user = req.user;
88
+ const exceededLimit = await getExceededTokenUsageLimit({
89
+ maxTokenUsage: options.maxTokenUsage,
90
+ req,
91
+ userID: user.id
92
+ });
93
+ if (exceededLimit) return Response.json({
94
+ error: "AI token usage limit reached."
95
+ }, {
96
+ status: 429
97
+ });
98
+ const managedProviders = options.providers?.length ? options.providers : null;
99
+ const requestedProvider = body.provider || (managedProviders ? managedProviders[0].id : user.aiProvider || "openai");
100
+ const managedProvider = managedProviders?.find((provider)=>provider.id === requestedProvider);
101
+ if (managedProviders && !managedProvider) {
102
+ return Response.json({
103
+ error: `Unsupported AI provider: ${requestedProvider}`
104
+ }, {
105
+ status: 400
106
+ });
107
+ }
108
+ if (!managedProvider && !isAIProvider(requestedProvider)) {
109
+ return Response.json({
110
+ error: `Unsupported AI provider: ${requestedProvider}`
111
+ }, {
112
+ status: 400
113
+ });
114
+ }
115
+ const provider = managedProvider?.provider || requestedProvider;
116
+ const requestedModel = body.model || managedProvider?.defaultModel;
117
+ const providerConfig = getProviderConfig({
118
+ apiKey: managedProvider ? managedProvider.apiKey : options.allowUserApiKeys === false ? null : user.aiApiKey,
119
+ defaultModels: options.models?.defaults,
120
+ model: requestedModel,
121
+ provider
122
+ });
123
+ if (!providerConfig.apiKey) return Response.json({
124
+ error: "Configure an AI provider API key first."
125
+ }, {
126
+ status: 400
127
+ });
128
+ const model = await getModel({
129
+ apiKey: providerConfig.apiKey,
130
+ ...managedProvider?.baseURL ? {
131
+ baseURL: managedProvider.baseURL
132
+ } : {},
133
+ model: providerConfig.modelID,
134
+ provider
135
+ });
136
+ const result = await generateText({
137
+ maxOutputTokens: options.maxOutputTokens || 700,
138
+ model,
139
+ prompt: JSON.stringify({
140
+ sourceLocale: defaultLocale,
141
+ targetLocale: locale,
142
+ values: missingSourceValues.map((entry, index)=>({
143
+ id: String(index),
144
+ value: entry.value
145
+ }))
146
+ }),
147
+ system: [
148
+ "Translate the human-readable content in every value to the target locale.",
149
+ "Preserve JSON structure, keys, IDs, URLs, numbers, booleans, nulls, and Lexical rich-text structure exactly.",
150
+ 'Return strict JSON in the shape {"translations":{"0":translatedValue}} with one entry for every input ID.',
151
+ "Return no Markdown or explanation."
152
+ ].join(" ")
153
+ });
154
+ const translatedValues = parseTranslations(result.text, missingSourceValues.length);
155
+ if (translatedValues.some((value)=>value === undefined)) throw new Error("Translation response is incomplete.");
156
+ if (result.usage && options.maxTokenUsage) {
157
+ await recordTokenUsage({
158
+ model: providerConfig.modelID,
159
+ provider: managedProvider?.id || provider,
160
+ req,
161
+ usage: result.usage,
162
+ userID: user.id
163
+ });
164
+ }
165
+ return Response.json({
166
+ available: true,
167
+ values: missingSourceValues.map((entry, index)=>({
168
+ fieldType: entry.fieldType,
169
+ path: entry.path,
170
+ value: translatedValues[index]
171
+ }))
172
+ });
173
+ } catch (error) {
174
+ req.payload.logger.error({
175
+ err: error,
176
+ msg: "AI document translation failed",
177
+ scopeSlug,
178
+ scopeType
179
+ });
180
+ return Response.json({
181
+ error: "AI document translation failed."
182
+ }, {
183
+ status: 500
184
+ });
185
+ }
186
+ };
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export type PayloadAIPluginOptions = {
19
19
  };
20
20
  models?: AIModelConfig;
21
21
  maxTokenUsage?: MaxTokenUsageOptions;
22
+ promptCaching?: boolean;
22
23
  providers?: AIProviderConfig[];
24
+ translate?: boolean;
23
25
  };
24
26
  export declare const payloadAiPlugin: (pluginOptions: PayloadAIPluginOptions) => (config: Config) => Config;
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { resolveMaxTokenUsageOptions, tokenUsageCollectionSlug } from "./ai/toke
3
3
  import { createApplyActionHandler } from "./handlers/applyActionHandler.js";
4
4
  import { createChatHandler } from "./handlers/chatHandler.js";
5
5
  import { createGenerateFieldHandler } from "./handlers/generateFieldHandler.js";
6
+ import { createTranslateDocumentHandler } from "./handlers/translateDocumentHandler.js";
6
7
  import { createMentionSuggestionHandler } from "./handlers/mentionSuggestionHandler.js";
7
8
  import { createMediaUploadHandler } from "./handlers/mediaUploadHandler.js";
8
9
  import { createProposalDiffHandler } from "./handlers/proposalDiffHandler.js";
@@ -10,6 +11,7 @@ import { createAuditLogHandler } from "./handlers/auditLogHandler.js";
10
11
  import { resolveCollectionPermissions } from "./payload/collectionPermissions.js";
11
12
  import { isInternalCollection } from "./payload/shared.js";
12
13
  import { addTextGenerationFields } from "./payload/textFieldGeneration.js";
14
+ import { hasLocalizedFields } from "./payload/documentTranslation.js";
13
15
  const resolveMediaUploadOptions = (media)=>{
14
16
  if (!media || media.enabled === false) return null;
15
17
  return {
@@ -225,6 +227,7 @@ const aiField = {
225
227
  }
226
228
  }
227
229
  };
230
+ const translateDocumentComponent = "@mvriu5/payload-ai/client#TranslateDocumentButton";
228
231
  const getEntityLabel = (label, fallback)=>{
229
232
  if (typeof label === "string") return label;
230
233
  if (label && typeof label === "object") {
@@ -233,8 +236,9 @@ const getEntityLabel = (label, fallback)=>{
233
236
  }
234
237
  return fallback;
235
238
  };
236
- const addAIFieldsToDocumentsAndGlobals = ({ addGenerateFields, addAIInput, config })=>{
239
+ const addAIFieldsToDocumentsAndGlobals = ({ addGenerateFields, addAIInput, addTranslation, config })=>{
237
240
  const pageContexts = new Map();
241
+ const translationPageContexts = new Map();
238
242
  for (const collection of config.collections || []){
239
243
  if (isInternalCollection(collection.slug)) continue;
240
244
  if (collection.slug === "payload-ai-auditlog") continue;
@@ -251,6 +255,24 @@ const addAIFieldsToDocumentsAndGlobals = ({ addGenerateFields, addAIInput, confi
251
255
  aiField,
252
256
  ...collection.fields || []
253
257
  ];
258
+ if (addTranslation && hasLocalizedFields(collection.fields || [])) {
259
+ translationPageContexts.set(`collection:${collection.slug}`, {
260
+ fields: collection.fields || [],
261
+ label: getEntityLabel(collection.labels?.singular, collection.slug),
262
+ slug: collection.slug,
263
+ type: "collection"
264
+ });
265
+ collection.admin = collection.admin || {};
266
+ collection.admin.components = collection.admin.components || {};
267
+ collection.admin.components.edit = collection.admin.components.edit || {};
268
+ const controls = collection.admin.components.edit.beforeDocumentControls || [];
269
+ if (!controls.includes(translateDocumentComponent)) {
270
+ collection.admin.components.edit.beforeDocumentControls = [
271
+ ...controls,
272
+ translateDocumentComponent
273
+ ];
274
+ }
275
+ }
254
276
  }
255
277
  for (const global of config.globals || []){
256
278
  if (addGenerateFields) {
@@ -266,8 +288,29 @@ const addAIFieldsToDocumentsAndGlobals = ({ addGenerateFields, addAIInput, confi
266
288
  aiField,
267
289
  ...global.fields || []
268
290
  ];
291
+ if (addTranslation && hasLocalizedFields(global.fields || [])) {
292
+ translationPageContexts.set(`global:${global.slug}`, {
293
+ fields: global.fields || [],
294
+ label: getEntityLabel(global.label, global.slug),
295
+ slug: global.slug,
296
+ type: "global"
297
+ });
298
+ global.admin = global.admin || {};
299
+ global.admin.components = global.admin.components || {};
300
+ global.admin.components.elements = global.admin.components.elements || {};
301
+ const controls = global.admin.components.elements.beforeDocumentControls || [];
302
+ if (!controls.includes(translateDocumentComponent)) {
303
+ global.admin.components.elements.beforeDocumentControls = [
304
+ ...controls,
305
+ translateDocumentComponent
306
+ ];
307
+ }
308
+ }
269
309
  }
270
- return pageContexts;
310
+ return {
311
+ textGenerationPageContexts: pageContexts,
312
+ translationPageContexts
313
+ };
271
314
  };
272
315
  export const payloadAiPlugin = (pluginOptions)=>(config)=>{
273
316
  const incomingOnInit = config.onInit;
@@ -292,9 +335,10 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
292
335
  });
293
336
  if (pluginOptions.disabled) return config;
294
337
  const generateFields = pluginOptions.generateFields !== false;
295
- const textGenerationPageContexts = addAIFieldsToDocumentsAndGlobals({
338
+ const { textGenerationPageContexts, translationPageContexts } = addAIFieldsToDocumentsAndGlobals({
296
339
  addGenerateFields: generateFields,
297
340
  addAIInput: pluginOptions.aiInput !== false,
341
+ addTranslation: pluginOptions.translate !== false,
298
342
  config
299
343
  });
300
344
  const mentionCollectionSlugs = config.collections.flatMap((collection)=>{
@@ -333,6 +377,7 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
333
377
  maxOutputTokens,
334
378
  maxTokenUsage,
335
379
  models: modelConfig,
380
+ promptCaching: pluginOptions.promptCaching !== false,
336
381
  providers: providerConfigs
337
382
  }),
338
383
  method: "post",
@@ -381,6 +426,20 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
381
426
  path: "/ai-generate-field"
382
427
  });
383
428
  }
429
+ if (translationPageContexts.size > 0) {
430
+ config.endpoints.push({
431
+ handler: createTranslateDocumentHandler({
432
+ allowUserApiKeys,
433
+ maxOutputTokens,
434
+ maxTokenUsage,
435
+ models: modelConfig,
436
+ pageContexts: translationPageContexts,
437
+ providers: providerConfigs
438
+ }),
439
+ method: "post",
440
+ path: "/ai-translate-document"
441
+ });
442
+ }
384
443
  if (mediaUploadOptions) {
385
444
  config.endpoints.push({
386
445
  handler: createMediaUploadHandler(mediaUploadOptions),
@@ -0,0 +1,15 @@
1
+ import type { Field } from "payload";
2
+ export type TranslationFieldType = "json" | "richText" | "text" | "textarea" | "unknown";
3
+ export type TranslationPageContext = {
4
+ fields: Field[];
5
+ label: string;
6
+ slug: string;
7
+ type: "collection" | "global";
8
+ };
9
+ export type TranslationValue = {
10
+ fieldType: TranslationFieldType;
11
+ path: string;
12
+ value: unknown;
13
+ };
14
+ export declare const getTranslationValues: (fields: Field[], data: unknown) => TranslationValue[];
15
+ export declare const hasLocalizedFields: (fields: Field[]) => boolean;
@@ -0,0 +1,120 @@
1
+ const hasValue = (value)=>{
2
+ if (value === null || value === undefined || value === "") return false;
3
+ if (Array.isArray(value)) return value.some(hasValue);
4
+ if (typeof value === "object") return Object.values(value).some(hasValue);
5
+ return true;
6
+ };
7
+ const hasRichTextValue = (value)=>{
8
+ if (!value || typeof value !== "object") return false;
9
+ if (Array.isArray(value)) return value.some(hasRichTextValue);
10
+ const node = value;
11
+ if (node.type === "text") return typeof node.text === "string" && node.text.trim().length > 0;
12
+ if ([
13
+ "block",
14
+ "inlineBlock",
15
+ "relationship",
16
+ "upload"
17
+ ].includes(String(node.type))) return true;
18
+ if (node.root) return hasRichTextValue(node.root);
19
+ return Array.isArray(node.children) && node.children.some(hasRichTextValue);
20
+ };
21
+ const getFieldType = (field)=>[
22
+ "json",
23
+ "richText",
24
+ "text",
25
+ "textarea"
26
+ ].includes(field.type) ? field.type : "unknown";
27
+ const collectFields = ({ data, fields, parentPath = "", result })=>{
28
+ for (const field of fields){
29
+ if (field.type === "tabs") {
30
+ for (const tab of field.tabs){
31
+ const tabData = "name" in tab && tab.name ? data[tab.name] || {} : data;
32
+ collectFields({
33
+ data: tabData,
34
+ fields: tab.fields,
35
+ parentPath: "name" in tab && tab.name ? parentPath ? `${parentPath}.${tab.name}` : tab.name : parentPath,
36
+ result
37
+ });
38
+ }
39
+ continue;
40
+ }
41
+ if (!("name" in field) || !field.name) {
42
+ if ("fields" in field && Array.isArray(field.fields)) collectFields({
43
+ data,
44
+ fields: field.fields,
45
+ parentPath,
46
+ result
47
+ });
48
+ continue;
49
+ }
50
+ const path = parentPath ? `${parentPath}.${field.name}` : field.name;
51
+ const value = data[field.name];
52
+ if ("localized" in field && field.localized) {
53
+ const valueExists = field.type === "richText" ? hasRichTextValue(value) : hasValue(value);
54
+ if (valueExists) {
55
+ result.push({
56
+ fieldType: getFieldType(field),
57
+ path,
58
+ value
59
+ });
60
+ }
61
+ continue;
62
+ }
63
+ if (field.type === "array" && Array.isArray(value)) {
64
+ value.forEach((row, index)=>{
65
+ if (row && typeof row === "object") {
66
+ collectFields({
67
+ data: row,
68
+ fields: field.fields,
69
+ parentPath: `${path}.${index}`,
70
+ result
71
+ });
72
+ }
73
+ });
74
+ continue;
75
+ }
76
+ if (field.type === "blocks" && Array.isArray(value)) {
77
+ value.forEach((row, index)=>{
78
+ if (!row || typeof row !== "object") return;
79
+ const rowData = row;
80
+ const block = field.blocks.find((candidate)=>typeof candidate === "object" && candidate.slug === rowData.blockType);
81
+ if (block && typeof block === "object") {
82
+ collectFields({
83
+ data: rowData,
84
+ fields: block.fields,
85
+ parentPath: `${path}.${index}`,
86
+ result
87
+ });
88
+ }
89
+ });
90
+ continue;
91
+ }
92
+ if ("fields" in field && Array.isArray(field.fields) && value && typeof value === "object") {
93
+ collectFields({
94
+ data: value,
95
+ fields: field.fields,
96
+ parentPath: path,
97
+ result
98
+ });
99
+ }
100
+ }
101
+ };
102
+ export const getTranslationValues = (fields, data)=>{
103
+ if (!data || typeof data !== "object") return [];
104
+ const result = [];
105
+ collectFields({
106
+ data: data,
107
+ fields,
108
+ result
109
+ });
110
+ return result;
111
+ };
112
+ export const hasLocalizedFields = (fields)=>{
113
+ for (const field of fields){
114
+ if ("localized" in field && field.localized) return true;
115
+ if (field.type === "tabs" && field.tabs.some((tab)=>hasLocalizedFields(tab.fields))) return true;
116
+ if (field.type === "blocks" && field.blocks.some((block)=>typeof block === "object" && hasLocalizedFields(block.fields))) return true;
117
+ if ("fields" in field && Array.isArray(field.fields) && hasLocalizedFields(field.fields)) return true;
118
+ }
119
+ return false;
120
+ };
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ import { type CollectionConfig } from "./normalizeData.js";
3
+ export declare const maxToolSelectedFields = 12;
4
+ export type ReadCollectionConfig = CollectionConfig & {
5
+ admin?: {
6
+ useAsTitle?: string;
7
+ };
8
+ timestamps?: boolean;
9
+ upload?: boolean | Record<string, unknown>;
10
+ };
11
+ export declare const getToolSelectableFieldNames: (config: ReadCollectionConfig) => string[];
12
+ export declare const createToolFieldNamesSchema: (configs: ReadCollectionConfig[]) => z.ZodOptional<z.ZodArray<z.ZodString | z.ZodEnum<{
13
+ [x: string]: string;
14
+ }>>>;
15
+ export declare const resolveToolFieldSelection: ({ config, mode, requestedFields, }: {
16
+ config: ReadCollectionConfig;
17
+ mode?: "document" | "search";
18
+ requestedFields?: string[];
19
+ }) => {
20
+ allowedFields: string[];
21
+ depth: number;
22
+ fields: string[];
23
+ invalidFields: string[];
24
+ select: Record<string, true>;
25
+ };
@@ -0,0 +1,118 @@
1
+ import { z } from "zod";
2
+ import { isSensitiveKey } from "../ai/sensitiveData.js";
3
+ import { getSchemaFields } from "./normalizeData.js";
4
+ export const maxToolSelectedFields = 12;
5
+ const cheapFieldTypes = new Set([
6
+ "checkbox",
7
+ "date",
8
+ "email",
9
+ "number",
10
+ "point",
11
+ "radio",
12
+ "select",
13
+ "text"
14
+ ]);
15
+ const populatedFieldTypes = new Set([
16
+ "relationship",
17
+ "upload"
18
+ ]);
19
+ const systemFieldNames = [
20
+ "id",
21
+ "createdAt",
22
+ "updatedAt"
23
+ ];
24
+ const uploadFieldNames = [
25
+ "filename",
26
+ "mimeType",
27
+ "filesize",
28
+ "width",
29
+ "height",
30
+ "url",
31
+ "thumbnailURL"
32
+ ];
33
+ const getSelectableFields = (config)=>{
34
+ return getSchemaFields(config).filter((field)=>{
35
+ return Boolean(field.name && field.type !== "ui" && !isSensitiveKey(field.name));
36
+ });
37
+ };
38
+ export const getToolSelectableFieldNames = (config)=>{
39
+ const names = getSelectableFields(config).flatMap((field)=>field.name ? [
40
+ field.name
41
+ ] : []);
42
+ names.push("id");
43
+ if (config.timestamps !== false) names.push("createdAt", "updatedAt");
44
+ if (config.upload) names.push(...uploadFieldNames);
45
+ return [
46
+ ...new Set(names)
47
+ ];
48
+ };
49
+ const getDefaultFieldNames = (config, mode)=>{
50
+ const selectableFields = getSelectableFields(config);
51
+ const selectableNames = new Set(getToolSelectableFieldNames(config));
52
+ const candidates = [
53
+ config.admin?.useAsTitle,
54
+ "title",
55
+ "name",
56
+ "label",
57
+ "headline",
58
+ "slug",
59
+ "_status",
60
+ ...config.upload ? selectableFields.flatMap((field)=>field.name && cheapFieldTypes.has(field.type || "") ? [
61
+ field.name
62
+ ] : []).slice(0, 2) : [],
63
+ ...mode === "document" ? [
64
+ "updatedAt"
65
+ ] : [],
66
+ ...config.upload ? [
67
+ "filename",
68
+ "mimeType",
69
+ "filesize",
70
+ "url"
71
+ ] : []
72
+ ];
73
+ const defaults = candidates.filter((name)=>Boolean(name && (selectableNames.has(name) || systemFieldNames.includes(name))));
74
+ if (!defaults.some((fieldName)=>!systemFieldNames.includes(fieldName) && !uploadFieldNames.includes(fieldName))) {
75
+ const firstCheapField = selectableFields.find((field)=>field.name && cheapFieldTypes.has(field.type || ""));
76
+ const firstSelectableField = firstCheapField || selectableFields[0];
77
+ defaults.unshift(firstSelectableField?.name || "id");
78
+ }
79
+ return [
80
+ ...new Set(defaults)
81
+ ].slice(0, mode === "search" ? 4 : 6);
82
+ };
83
+ export const createToolFieldNamesSchema = (configs)=>{
84
+ const names = [
85
+ ...new Set(configs.flatMap(getToolSelectableFieldNames))
86
+ ];
87
+ const fieldNameSchema = names.length > 0 ? z.enum(names) : z.string().refine(()=>false, "No fields are selectable.");
88
+ return z.array(fieldNameSchema).max(maxToolSelectedFields).optional();
89
+ };
90
+ export const resolveToolFieldSelection = ({ config, mode = "document", requestedFields })=>{
91
+ const allowedNames = new Set(getToolSelectableFieldNames(config));
92
+ const normalizedRequestedFields = [
93
+ ...new Set((requestedFields || []).slice(0, maxToolSelectedFields).map((field)=>field.trim()).filter(Boolean))
94
+ ];
95
+ const invalidFields = normalizedRequestedFields.filter((field)=>!allowedNames.has(field));
96
+ const selectedFields = [
97
+ ...new Set((requestedFields?.length ? requestedFields : getDefaultFieldNames(config, mode)).map((field)=>field.trim()).filter((field)=>allowedNames.has(field)))
98
+ ].slice(0, maxToolSelectedFields);
99
+ const fields = selectedFields.length > 0 ? selectedFields : getDefaultFieldNames(config, mode);
100
+ const fieldTypesByName = new Map(getSelectableFields(config).flatMap((field)=>field.name ? [
101
+ [
102
+ field.name,
103
+ field.type
104
+ ]
105
+ ] : []));
106
+ return {
107
+ allowedFields: [
108
+ ...allowedNames
109
+ ],
110
+ depth: fields.some((field)=>populatedFieldTypes.has(fieldTypesByName.get(field) || "")) ? 1 : 0,
111
+ fields,
112
+ invalidFields,
113
+ select: Object.fromEntries(fields.map((field)=>[
114
+ field,
115
+ true
116
+ ]))
117
+ };
118
+ };
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ import { type CollectionConfig } from "./normalizeData.js";
3
+ export declare const genericPayloadDataSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
4
+ export declare const createPayloadDataSchema: (config?: CollectionConfig | null) => z.ZodRecord<z.ZodString, z.ZodUnknown> | z.ZodObject<{
5
+ [x: string]: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
6
+ }, z.core.$strict>;
7
+ export declare const createLocalizedPayloadDataSchema: (dataSchema: z.ZodType) => z.ZodRecord<z.ZodString, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;