@humaan/payload-plugin-ai-assistant 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +277 -2
  2. package/dist/admin/AiAssistantControl.d.ts +11 -0
  3. package/dist/admin/AiAssistantControl.js +31 -0
  4. package/dist/admin/AiAssistantControl.js.map +1 -0
  5. package/dist/admin/AiAssistantPanel.d.ts +10 -0
  6. package/dist/admin/AiAssistantPanel.js +208 -0
  7. package/dist/admin/AiAssistantPanel.js.map +1 -0
  8. package/dist/admin/AiAssistantPanel.module.scss +689 -0
  9. package/dist/admin/AiSourceSwitch.d.ts +13 -0
  10. package/dist/admin/AiSourceSwitch.js +14 -0
  11. package/dist/admin/AiSourceSwitch.js.map +1 -0
  12. package/dist/admin/BudgetField.d.ts +8 -0
  13. package/dist/admin/BudgetField.js +145 -0
  14. package/dist/admin/BudgetField.js.map +1 -0
  15. package/dist/admin/BudgetField.module.scss +47 -0
  16. package/dist/admin/Conversation.d.ts +12 -0
  17. package/dist/admin/Conversation.js +469 -0
  18. package/dist/admin/Conversation.js.map +1 -0
  19. package/dist/admin/Markdown.d.ts +9 -0
  20. package/dist/admin/Markdown.js +63 -0
  21. package/dist/admin/Markdown.js.map +1 -0
  22. package/dist/admin/PageContextBridge.d.ts +5 -0
  23. package/dist/admin/PageContextBridge.js +72 -0
  24. package/dist/admin/PageContextBridge.js.map +1 -0
  25. package/dist/admin/Pill.d.ts +11 -0
  26. package/dist/admin/Pill.js +27 -0
  27. package/dist/admin/Pill.js.map +1 -0
  28. package/dist/admin/PlanCard.d.ts +14 -0
  29. package/dist/admin/PlanCard.js +465 -0
  30. package/dist/admin/PlanCard.js.map +1 -0
  31. package/dist/admin/SettingsAlerts.d.ts +7 -0
  32. package/dist/admin/SettingsAlerts.js +37 -0
  33. package/dist/admin/SettingsAlerts.js.map +1 -0
  34. package/dist/admin/UsageSummary.d.ts +11 -0
  35. package/dist/admin/UsageSummary.js +129 -0
  36. package/dist/admin/UsageSummary.js.map +1 -0
  37. package/dist/admin/attachmentUpload.d.ts +25 -0
  38. package/dist/admin/attachmentUpload.js +76 -0
  39. package/dist/admin/attachmentUpload.js.map +1 -0
  40. package/dist/admin/openDocument.d.ts +13 -0
  41. package/dist/admin/openDocument.js +34 -0
  42. package/dist/admin/openDocument.js.map +1 -0
  43. package/dist/admin/percent.d.ts +10 -0
  44. package/dist/admin/percent.js +21 -0
  45. package/dist/admin/percent.js.map +1 -0
  46. package/dist/admin/threads.d.ts +21 -0
  47. package/dist/admin/threads.js +92 -0
  48. package/dist/admin/threads.js.map +1 -0
  49. package/dist/admin/toolSteps.d.ts +12 -0
  50. package/dist/admin/toolSteps.js +57 -0
  51. package/dist/admin/toolSteps.js.map +1 -0
  52. package/dist/admin/transport.d.ts +24 -0
  53. package/dist/admin/transport.js +75 -0
  54. package/dist/admin/transport.js.map +1 -0
  55. package/dist/admin/useAttachments.d.ts +32 -0
  56. package/dist/admin/useAttachments.js +91 -0
  57. package/dist/admin/useAttachments.js.map +1 -0
  58. package/dist/admin/useEntityLabel.d.ts +3 -0
  59. package/dist/admin/useEntityLabel.js +23 -0
  60. package/dist/admin/useEntityLabel.js.map +1 -0
  61. package/dist/attachments/collection.d.ts +15 -0
  62. package/dist/attachments/collection.js +90 -0
  63. package/dist/attachments/collection.js.map +1 -0
  64. package/dist/attachments/conversation.d.ts +25 -0
  65. package/dist/attachments/conversation.js +105 -0
  66. package/dist/attachments/conversation.js.map +1 -0
  67. package/dist/attachments/rules.d.ts +9 -0
  68. package/dist/attachments/rules.js +17 -0
  69. package/dist/attachments/rules.js.map +1 -0
  70. package/dist/attachments/store.d.ts +27 -0
  71. package/dist/attachments/store.js +187 -0
  72. package/dist/attachments/store.js.map +1 -0
  73. package/dist/billing.d.ts +60 -0
  74. package/dist/billing.js +116 -0
  75. package/dist/billing.js.map +1 -0
  76. package/dist/chat/history.d.ts +37 -0
  77. package/dist/chat/history.js +165 -0
  78. package/dist/chat/history.js.map +1 -0
  79. package/dist/chat/messages.d.ts +5 -0
  80. package/dist/chat/messages.js +3 -0
  81. package/dist/chat/messages.js.map +1 -0
  82. package/dist/chat/threads.d.ts +33 -0
  83. package/dist/chat/threads.js +77 -0
  84. package/dist/chat/threads.js.map +1 -0
  85. package/dist/chat/tools.d.ts +76 -0
  86. package/dist/chat/tools.js +596 -0
  87. package/dist/chat/tools.js.map +1 -0
  88. package/dist/chat/usage.d.ts +66 -0
  89. package/dist/chat/usage.js +219 -0
  90. package/dist/chat/usage.js.map +1 -0
  91. package/dist/collectionAccess.d.ts +6 -0
  92. package/dist/collectionAccess.js +32 -0
  93. package/dist/collectionAccess.js.map +1 -0
  94. package/dist/constants.d.ts +64 -0
  95. package/dist/constants.js +51 -0
  96. package/dist/constants.js.map +1 -0
  97. package/dist/endpoints/access.d.ts +4 -0
  98. package/dist/endpoints/access.js +28 -0
  99. package/dist/endpoints/access.js.map +1 -0
  100. package/dist/endpoints/allowance.d.ts +22 -0
  101. package/dist/endpoints/allowance.js +37 -0
  102. package/dist/endpoints/allowance.js.map +1 -0
  103. package/dist/endpoints/applyEndpoint.d.ts +12 -0
  104. package/dist/endpoints/applyEndpoint.js +170 -0
  105. package/dist/endpoints/applyEndpoint.js.map +1 -0
  106. package/dist/endpoints/budget.d.ts +30 -0
  107. package/dist/endpoints/budget.js +153 -0
  108. package/dist/endpoints/budget.js.map +1 -0
  109. package/dist/endpoints/chatEndpoint.d.ts +12 -0
  110. package/dist/endpoints/chatEndpoint.js +295 -0
  111. package/dist/endpoints/chatEndpoint.js.map +1 -0
  112. package/dist/endpoints/form.d.ts +23 -0
  113. package/dist/endpoints/form.js +95 -0
  114. package/dist/endpoints/form.js.map +1 -0
  115. package/dist/endpoints/usageEndpoint.d.ts +13 -0
  116. package/dist/endpoints/usageEndpoint.js +70 -0
  117. package/dist/endpoints/usageEndpoint.js.map +1 -0
  118. package/dist/index.d.ts +4 -0
  119. package/dist/index.js +5 -0
  120. package/dist/index.js.map +1 -0
  121. package/dist/lexical/editorConfig.d.ts +4 -0
  122. package/dist/lexical/editorConfig.js +19 -0
  123. package/dist/lexical/editorConfig.js.map +1 -0
  124. package/dist/lexical/lexicalToMarkdown.d.ts +10 -0
  125. package/dist/lexical/lexicalToMarkdown.js +10 -0
  126. package/dist/lexical/lexicalToMarkdown.js.map +1 -0
  127. package/dist/lexical/markdownToLexical.d.ts +7 -0
  128. package/dist/lexical/markdownToLexical.js +10 -0
  129. package/dist/lexical/markdownToLexical.js.map +1 -0
  130. package/dist/plan/allowed.d.ts +7 -0
  131. package/dist/plan/allowed.js +19 -0
  132. package/dist/plan/allowed.js.map +1 -0
  133. package/dist/plan/applyPlan.d.ts +16 -0
  134. package/dist/plan/applyPlan.js +353 -0
  135. package/dist/plan/applyPlan.js.map +1 -0
  136. package/dist/plan/catalogue.d.ts +37 -0
  137. package/dist/plan/catalogue.js +157 -0
  138. package/dist/plan/catalogue.js.map +1 -0
  139. package/dist/plan/diff.d.ts +13 -0
  140. package/dist/plan/diff.js +87 -0
  141. package/dist/plan/diff.js.map +1 -0
  142. package/dist/plan/parsePlan.d.ts +27 -0
  143. package/dist/plan/parsePlan.js +322 -0
  144. package/dist/plan/parsePlan.js.map +1 -0
  145. package/dist/plan/prompt.d.ts +1 -0
  146. package/dist/plan/prompt.js +45 -0
  147. package/dist/plan/prompt.js.map +1 -0
  148. package/dist/plan/stored.d.ts +53 -0
  149. package/dist/plan/stored.js +3 -0
  150. package/dist/plan/stored.js.map +1 -0
  151. package/dist/plan/tokens.d.ts +8 -0
  152. package/dist/plan/tokens.js +79 -0
  153. package/dist/plan/tokens.js.map +1 -0
  154. package/dist/plan/view.d.ts +4 -0
  155. package/dist/plan/view.js +43 -0
  156. package/dist/plan/view.js.map +1 -0
  157. package/dist/plugin.d.ts +3 -0
  158. package/dist/plugin.js +464 -0
  159. package/dist/plugin.js.map +1 -0
  160. package/dist/read/outline.d.ts +7 -0
  161. package/dist/read/outline.js +70 -0
  162. package/dist/read/outline.js.map +1 -0
  163. package/dist/read/readTarget.d.ts +4 -0
  164. package/dist/read/readTarget.js +13 -0
  165. package/dist/read/readTarget.js.map +1 -0
  166. package/dist/read/readable.d.ts +8 -0
  167. package/dist/read/readable.js +131 -0
  168. package/dist/read/readable.js.map +1 -0
  169. package/dist/runtime.d.ts +15 -0
  170. package/dist/runtime.js +14 -0
  171. package/dist/runtime.js.map +1 -0
  172. package/dist/schema/fields.d.ts +113 -0
  173. package/dist/schema/fields.js +1036 -0
  174. package/dist/schema/fields.js.map +1 -0
  175. package/dist/schema/listingFields.d.ts +9 -0
  176. package/dist/schema/listingFields.js +95 -0
  177. package/dist/schema/listingFields.js.map +1 -0
  178. package/dist/schema/slugify.d.ts +2 -0
  179. package/dist/schema/slugify.js +5 -0
  180. package/dist/schema/slugify.js.map +1 -0
  181. package/dist/settings.d.ts +10 -0
  182. package/dist/settings.js +54 -0
  183. package/dist/settings.js.map +1 -0
  184. package/dist/types.d.ts +198 -0
  185. package/dist/types.js +6 -0
  186. package/dist/types.js.map +1 -0
  187. package/dist/uploads/routeAsset.d.ts +27 -0
  188. package/dist/uploads/routeAsset.js +127 -0
  189. package/dist/uploads/routeAsset.js.map +1 -0
  190. package/package.json +122 -3
@@ -0,0 +1,596 @@
1
+ import { jsonSchema, tool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { aiAssistantPlanSlug, maxReadCharacters } from '../constants.js';
4
+ import { notAllowed } from '../plan/allowed.js';
5
+ import { canChangeTarget, collectionFields, describeTarget, globalFields, planTargets, searchWhere, titleFieldOf } from '../plan/catalogue.js';
6
+ import { fieldChanges } from '../plan/diff.js';
7
+ import { parsePlan } from '../plan/parsePlan.js';
8
+ import { valueAtPath } from '../plan/tokens.js';
9
+ import { planView } from '../plan/view.js';
10
+ import { outline, valueAtReadablePath } from '../read/outline.js';
11
+ import { readableTarget } from '../read/readTarget.js';
12
+ import { compiledFieldAtPath, followedSlug, unknownFieldPaths } from '../schema/fields.js';
13
+ import { missingListingCreatePaths } from '../schema/listingFields.js';
14
+ import { userIdOf } from '../settings.js';
15
+ /**
16
+ * The full plan shape, so the model gets it right first time. parsePlan still checks every value,
17
+ * since a schema only describes what the model should send.
18
+ */ const planInput = jsonSchema({
19
+ type: 'object',
20
+ additionalProperties: false,
21
+ required: [
22
+ 'summary',
23
+ 'blocked',
24
+ 'operations'
25
+ ],
26
+ properties: {
27
+ summary: {
28
+ type: 'string',
29
+ description: 'What the plan changes, in one or two sentences.'
30
+ },
31
+ blocked: {
32
+ type: 'boolean',
33
+ description: 'True when the editor must choose first. Then operations is empty.'
34
+ },
35
+ questions: {
36
+ type: 'array',
37
+ items: {
38
+ type: 'string'
39
+ }
40
+ },
41
+ candidates: {
42
+ type: 'array',
43
+ items: {
44
+ type: 'object',
45
+ required: [
46
+ 'kind',
47
+ 'slug',
48
+ 'label'
49
+ ],
50
+ properties: {
51
+ kind: {
52
+ type: 'string',
53
+ enum: [
54
+ 'collection',
55
+ 'global'
56
+ ]
57
+ },
58
+ slug: {
59
+ type: 'string'
60
+ },
61
+ id: {
62
+ type: 'string'
63
+ },
64
+ label: {
65
+ type: 'string'
66
+ }
67
+ }
68
+ }
69
+ },
70
+ operations: {
71
+ type: 'array',
72
+ items: {
73
+ type: 'object',
74
+ required: [
75
+ 'action',
76
+ 'kind',
77
+ 'slug',
78
+ 'label',
79
+ 'summary'
80
+ ],
81
+ properties: {
82
+ action: {
83
+ type: 'string',
84
+ enum: [
85
+ 'create',
86
+ 'update'
87
+ ]
88
+ },
89
+ kind: {
90
+ type: 'string',
91
+ enum: [
92
+ 'collection',
93
+ 'global'
94
+ ]
95
+ },
96
+ slug: {
97
+ type: 'string',
98
+ description: 'Collection or global slug.'
99
+ },
100
+ id: {
101
+ type: 'string',
102
+ description: 'Required to update a collection document.'
103
+ },
104
+ label: {
105
+ type: 'string',
106
+ description: "The document's title, for the plan card."
107
+ },
108
+ summary: {
109
+ type: 'string',
110
+ description: 'What this operation changes, and whether it saves as a draft.'
111
+ },
112
+ set: {
113
+ type: 'object',
114
+ description: 'Field values, nested as in the field guide.'
115
+ },
116
+ append: {
117
+ type: 'array',
118
+ description: 'Blocks to add to the end of a blocks field.',
119
+ items: {
120
+ type: 'object',
121
+ required: [
122
+ 'path',
123
+ 'blocks'
124
+ ],
125
+ properties: {
126
+ path: {
127
+ type: 'string',
128
+ description: 'Dotted path to the blocks field, such as content.content.'
129
+ },
130
+ blocks: {
131
+ type: 'array',
132
+ items: {
133
+ type: 'object'
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ },
142
+ files: {
143
+ type: 'array',
144
+ description: 'Attachments this plan saves. Only listed files are saved.',
145
+ items: {
146
+ type: 'object',
147
+ required: [
148
+ 'index'
149
+ ],
150
+ properties: {
151
+ index: {
152
+ type: 'integer'
153
+ },
154
+ alt: {
155
+ type: 'string',
156
+ description: 'Required for images.'
157
+ },
158
+ title: {
159
+ type: 'string'
160
+ },
161
+ note: {
162
+ type: 'string'
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ });
169
+ /**
170
+ * Every tool reads with the editor's access. Only submitPlan writes, and it writes a plan, not the documents.
171
+ */ export function assistantTools({ req, chatId, collectionRules, files, onPlan, openDocument }) {
172
+ const targets = planTargets(req.payload.config);
173
+ return {
174
+ listTargets: tool({
175
+ description: 'List the collections and globals. canChange says whether a plan may change it; the others can still be read.',
176
+ inputSchema: z.object({}),
177
+ execute: ()=>({
178
+ targets: targets.map((target)=>({
179
+ slug: target.slug,
180
+ kind: target.kind,
181
+ label: target.label,
182
+ drafts: target.drafts,
183
+ upload: target.upload,
184
+ canChange: canChangeTarget(req.payload.config, target.kind, target.slug)
185
+ }))
186
+ })
187
+ }),
188
+ describeTarget: tool({
189
+ description: 'Return the field guide for one collection or global. Call this before writing its fields. Blocks are listed by name; pass blocks to get the fields of the ones you will write.',
190
+ inputSchema: z.object({
191
+ kind: z.enum([
192
+ 'collection',
193
+ 'global'
194
+ ]),
195
+ slug: z.string(),
196
+ blocks: z.array(z.string()).max(12).optional().describe('Block slugs to describe in full.')
197
+ }),
198
+ execute: ({ kind, slug, blocks })=>describeTarget(req.payload.config, kind, slug, collectionRules, blocks)
199
+ }),
200
+ searchDocuments: tool({
201
+ description: "Search one collection by its title or name, and filter by any field from its field guide, such as a category or relationship id. An empty query lists recent documents. If more than one result could be the editor's target, do not pick one.",
202
+ inputSchema: z.object({
203
+ collection: z.string(),
204
+ query: z.string(),
205
+ filters: z.array(z.object({
206
+ field: z.string().regex(/^[\w.]+$/).describe('Field path from the field guide, such as category.'),
207
+ operator: z.enum([
208
+ 'equals',
209
+ 'not_equals',
210
+ 'in',
211
+ 'like',
212
+ 'exists'
213
+ ]),
214
+ value: z.union([
215
+ z.string(),
216
+ z.number(),
217
+ z.boolean(),
218
+ z.array(z.union([
219
+ z.string(),
220
+ z.number()
221
+ ]))
222
+ ])
223
+ })).max(5).optional(),
224
+ limit: z.number().int().min(1).max(25).optional()
225
+ }),
226
+ execute: (input)=>searchDocuments(req, input.collection, input.query, input.filters, input.limit)
227
+ }),
228
+ readDocument: tool({
229
+ description: 'Read one document, or a global, with rich text as markdown. A long document comes back as an outline; pass a path, such as content.content.3, to read one part in full.',
230
+ inputSchema: z.object({
231
+ kind: z.enum([
232
+ 'collection',
233
+ 'global'
234
+ ]),
235
+ slug: z.string(),
236
+ id: z.string().optional(),
237
+ path: z.string().optional().describe('Dotted path to one part of the document. List items are numbered from 0.')
238
+ }),
239
+ execute: (input)=>readForModel(req, input, openDocument)
240
+ }),
241
+ submitPlan: tool({
242
+ description: 'Submit a plan for the editor to review. This does not save the changes. If this returns an error, correct the plan and call it again.',
243
+ inputSchema: planInput,
244
+ execute: async (input)=>{
245
+ const parsed = parsePlan(input, targets, files);
246
+ if (!parsed.ok) {
247
+ return {
248
+ ok: false,
249
+ error: parsed.errors.join(' ')
250
+ };
251
+ }
252
+ const problem = notAllowed(parsed.plan, req.payload.config) ?? unknownFields(req, parsed.plan) ?? missingListingFields(req, parsed.plan) ?? await missingDocuments(req, parsed.plan);
253
+ if (problem) {
254
+ return {
255
+ ok: false,
256
+ error: problem
257
+ };
258
+ }
259
+ const status = parsed.plan.blocked ? 'blocked' : 'proposed';
260
+ // Plans are written only here and by the apply endpoints, so a plan always comes from the model and its checks.
261
+ const saved = await req.payload.create({
262
+ collection: aiAssistantPlanSlug,
263
+ data: {
264
+ owner: userIdOf(req.user),
265
+ thread: chatId,
266
+ status,
267
+ summary: parsed.plan.summary,
268
+ plan: parsed.plan
269
+ },
270
+ req
271
+ });
272
+ onPlan();
273
+ const adminRoute = req.payload.config.routes?.admin ?? '/admin';
274
+ const changes = await planFieldChanges(req, parsed.plan);
275
+ return {
276
+ ok: true,
277
+ status,
278
+ plan: planView(String(saved.id), parsed.plan, adminRoute, changes)
279
+ };
280
+ },
281
+ // The editor sees each value beside the current one. The model wrote those values, so it gets the plan without them.
282
+ toModelOutput: ({ output })=>({
283
+ type: 'json',
284
+ value: withoutFieldChanges(output)
285
+ })
286
+ })
287
+ };
288
+ }
289
+ async function planFieldChanges(req, plan) {
290
+ return Promise.all(plan.operations.map(async (operation)=>{
291
+ const read = operation.action === 'update' ? await readDocument(req, operation.kind, operation.slug, operation.id) : undefined;
292
+ const current = read && 'document' in read ? read.document : undefined;
293
+ const changes = fieldChanges({
294
+ operation,
295
+ current,
296
+ files: plan.files,
297
+ operations: plan.operations
298
+ });
299
+ const slug = slugChange(req, operation, current);
300
+ return Promise.all([
301
+ ...changes,
302
+ ...slug ? [
303
+ slug
304
+ ] : []
305
+ ].map((change)=>withMedia(req, operation, change, current, plan.files)));
306
+ }));
307
+ }
308
+ /** A rename moves the URL when the slug follows the title, so the card shows it before Proceed. */ function slugChange(req, operation, current) {
309
+ if (operation.action !== 'update' || current === undefined) {
310
+ return undefined;
311
+ }
312
+ const fields = (operation.kind === 'collection' ? collectionFields(req.payload.config, operation.slug) : globalFields(req.payload.config, operation.slug)) ?? [];
313
+ const slug = followedSlug(fields, current, valueAtPath(operation.set, 'title'));
314
+ if (!slug) {
315
+ return undefined;
316
+ }
317
+ const before = valueAtPath(current, 'slug');
318
+ return {
319
+ path: 'slug',
320
+ ...typeof before === 'string' && before ? {
321
+ before
322
+ } : {},
323
+ after: `${slug} (follows the new title; the URL changes)`
324
+ };
325
+ }
326
+ /** Upload fields show the file on each side, read with the editor's access. */ async function withMedia(req, operation, change, current, files) {
327
+ if (change.added) {
328
+ return change;
329
+ }
330
+ const fields = (operation.kind === 'collection' ? collectionFields(req.payload.config, operation.slug) : globalFields(req.payload.config, operation.slug)) ?? [];
331
+ const field = compiledFieldAtPath(fields, change.path);
332
+ if (field?.kind !== 'upload' || field.hasMany) {
333
+ return change;
334
+ }
335
+ const [before, after] = await Promise.all([
336
+ mediaView(req, field.relationTo, current === undefined ? undefined : valueAtPath(current, change.path), files),
337
+ mediaView(req, field.relationTo, valueAtPath(operation.set, change.path), files)
338
+ ]);
339
+ if (!before && !after) {
340
+ return change;
341
+ }
342
+ return {
343
+ ...change,
344
+ media: {
345
+ ...before ? {
346
+ before
347
+ } : {},
348
+ ...after ? {
349
+ after
350
+ } : {}
351
+ }
352
+ };
353
+ }
354
+ async function mediaView(req, relationTo, value, files) {
355
+ if (value && typeof value === 'object' && '$file' in value) {
356
+ const file = files.find((item)=>item.index === value.$file);
357
+ return file ? {
358
+ label: file.title || file.name,
359
+ pending: true
360
+ } : undefined;
361
+ }
362
+ const reference = value && typeof value === 'object' && 'relationTo' in value && 'value' in value ? {
363
+ collection: String(value.relationTo),
364
+ id: value.value
365
+ } : {
366
+ collection: relationTo[0],
367
+ id: value
368
+ };
369
+ if (!reference.collection || typeof reference.id !== 'string' && typeof reference.id !== 'number') {
370
+ return undefined;
371
+ }
372
+ try {
373
+ const doc = await req.payload.findByID({
374
+ collection: reference.collection,
375
+ id: reference.id,
376
+ depth: 0,
377
+ overrideAccess: false,
378
+ req,
379
+ user: req.user
380
+ });
381
+ const text = (key)=>typeof doc[key] === 'string' && doc[key] ? doc[key] : undefined;
382
+ const isImage = text('mimeType')?.startsWith('image/');
383
+ return {
384
+ label: text('alt') ?? text('title') ?? text('filename') ?? String(reference.id),
385
+ ...isImage && (text('thumbnailURL') ?? text('url')) ? {
386
+ thumbnail: text('thumbnailURL') ?? text('url')
387
+ } : {}
388
+ };
389
+ } catch {
390
+ return undefined;
391
+ }
392
+ }
393
+ function withoutFieldChanges(output) {
394
+ if (!output.ok) {
395
+ return output;
396
+ }
397
+ const operations = output.plan.operations.map(({ fieldChanges: _shown, ...operation })=>operation);
398
+ return {
399
+ ...output,
400
+ plan: {
401
+ ...output.plan,
402
+ operations
403
+ }
404
+ };
405
+ }
406
+ async function searchDocuments(req, collection, query, filters = [], limit = 8) {
407
+ const target = planTargets(req.payload.config).find((item)=>item.kind === 'collection' && item.slug === collection);
408
+ if (!target) {
409
+ return {
410
+ error: `Unknown collection "${collection}".`
411
+ };
412
+ }
413
+ const titleKey = titleFieldOf(req.payload.config, collection);
414
+ try {
415
+ const found = await req.payload.find({
416
+ collection: collection,
417
+ where: searchWhere(req.payload.config, collection, query, filters),
418
+ sort: '-updatedAt',
419
+ limit,
420
+ depth: 0,
421
+ draft: true,
422
+ overrideAccess: false,
423
+ req,
424
+ user: req.user,
425
+ select: {
426
+ [titleKey]: true
427
+ }
428
+ });
429
+ return {
430
+ count: found.docs.length,
431
+ multiple: found.docs.length > 1,
432
+ documents: found.docs.map((doc)=>{
433
+ const record = doc;
434
+ const label = typeof record[titleKey] === 'string' ? record[titleKey] : String(record.id);
435
+ return {
436
+ id: String(record.id),
437
+ label
438
+ };
439
+ })
440
+ };
441
+ } catch (error) {
442
+ return {
443
+ error: error instanceof Error ? error.message : 'The search failed.'
444
+ };
445
+ }
446
+ }
447
+ /**
448
+ * What readDocument returns to the model. The open document is read from its form, so it includes unsaved changes.
449
+ * Long documents come back as an outline, and one part can be read in full by path.
450
+ */ async function readForModel(req, input, open) {
451
+ const isOpen = open?.context.kind === input.kind && open.context.slug === input.slug && (input.kind === 'global' || open.context.id === input.id);
452
+ const read = isOpen && open ? {
453
+ label: open.context.label,
454
+ document: open.document
455
+ } : await readDocument(req, input.kind, input.slug, input.id);
456
+ if ('error' in read) {
457
+ return read;
458
+ }
459
+ const path = input.path?.trim();
460
+ if (path) {
461
+ const value = valueAtReadablePath(read.document, path);
462
+ if (value === undefined) {
463
+ return {
464
+ error: `Nothing at ${path}. Read the document without a path to see its outline.`
465
+ };
466
+ }
467
+ const json = JSON.stringify(value) ?? '';
468
+ return json.length > maxReadCharacters ? {
469
+ label: read.label,
470
+ path,
471
+ outline: outline(value),
472
+ note: 'This part is long, so this is an outline. Read a deeper path for full values.'
473
+ } : {
474
+ label: read.label,
475
+ path,
476
+ value
477
+ };
478
+ }
479
+ if ((JSON.stringify(read.document) ?? '').length <= maxReadCharacters) {
480
+ return read;
481
+ }
482
+ return {
483
+ label: read.label,
484
+ outline: outline(read.document),
485
+ note: 'This document is long, so this is an outline. Call readDocument again with a path for full values.'
486
+ };
487
+ }
488
+ /** Reads with the editor's access, so the assistant sees only what the editor can see. */ export async function readDocument(req, kind, slug, id) {
489
+ const target = planTargets(req.payload.config).find((item)=>item.kind === kind && item.slug === slug);
490
+ if (!target) {
491
+ return {
492
+ error: `Unknown ${kind} "${slug}".`
493
+ };
494
+ }
495
+ try {
496
+ if (kind === 'global') {
497
+ const doc = await req.payload.findGlobal({
498
+ slug: slug,
499
+ depth: 0,
500
+ draft: true,
501
+ overrideAccess: false,
502
+ req,
503
+ user: req.user
504
+ });
505
+ return {
506
+ label: target.label,
507
+ document: await readableTarget(req.payload.config, kind, slug, doc)
508
+ };
509
+ }
510
+ if (!id) {
511
+ return {
512
+ error: 'An id is required to read a collection document.'
513
+ };
514
+ }
515
+ const doc = await req.payload.findByID({
516
+ collection: slug,
517
+ id,
518
+ depth: 0,
519
+ draft: true,
520
+ overrideAccess: false,
521
+ req,
522
+ user: req.user
523
+ });
524
+ const title = doc[titleFieldOf(req.payload.config, slug)];
525
+ return {
526
+ label: typeof title === 'string' && title ? title : id,
527
+ document: await readableTarget(req.payload.config, kind, slug, doc)
528
+ };
529
+ } catch (error) {
530
+ return {
531
+ error: error instanceof Error ? error.message : 'The document could not be read.'
532
+ };
533
+ }
534
+ }
535
+ function unknownFields(req, plan) {
536
+ for (const operation of plan.operations){
537
+ const fields = operation.kind === 'collection' ? collectionFields(req.payload.config, operation.slug) : globalFields(req.payload.config, operation.slug);
538
+ if (!fields) {
539
+ continue;
540
+ }
541
+ const unknown = unknownFieldPaths(fields, operation.set);
542
+ if (unknown.length > 0) {
543
+ return `${operation.label} (${operation.slug}) has no field ${unknown.join(', ')}. Use the names in the field guide, nested as objects.`;
544
+ }
545
+ }
546
+ return undefined;
547
+ }
548
+ function missingListingFields(req, plan) {
549
+ for (const operation of plan.operations){
550
+ if (operation.action !== 'create') {
551
+ continue;
552
+ }
553
+ const fields = operation.kind === 'collection' ? collectionFields(req.payload.config, operation.slug) : globalFields(req.payload.config, operation.slug);
554
+ if (!fields) {
555
+ continue;
556
+ }
557
+ const missing = missingListingCreatePaths(fields, operation.set);
558
+ if (missing.length === 0) {
559
+ continue;
560
+ }
561
+ return `When creating ${operation.label} (${operation.slug}), fill: ${missing.join(', ')}.`;
562
+ }
563
+ return undefined;
564
+ }
565
+ async function missingDocuments(req, plan) {
566
+ const checks = [
567
+ ...plan.operations.filter((operation)=>operation.action === 'update' && operation.kind === 'collection' && operation.id).map((operation)=>({
568
+ slug: operation.slug,
569
+ id: operation.id,
570
+ message: `No ${operation.slug} document has id ${operation.id}. Search again and use an id from the result.`
571
+ })),
572
+ ...plan.candidates.filter((candidate)=>candidate.kind === 'collection' && candidate.id).map((candidate)=>({
573
+ slug: candidate.slug,
574
+ id: candidate.id,
575
+ message: `Candidate ${candidate.label} was not found in ${candidate.slug}.`
576
+ }))
577
+ ];
578
+ for (const check of checks){
579
+ try {
580
+ await req.payload.findByID({
581
+ collection: check.slug,
582
+ id: check.id,
583
+ depth: 0,
584
+ draft: true,
585
+ overrideAccess: false,
586
+ req,
587
+ user: req.user
588
+ });
589
+ } catch {
590
+ return check.message;
591
+ }
592
+ }
593
+ return undefined;
594
+ }
595
+
596
+ //# sourceMappingURL=tools.js.map