@mvriu5/payload-ai 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -100
- 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.d.ts +1 -1
- package/dist/components/ai-input/AIInput.js +3 -1
- package/dist/components/generate-field/GenerateField.js +3 -2
- 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 +2 -0
- package/dist/exports/client.js +2 -0
- package/dist/handlers/chatHandler.d.ts +1 -0
- package/dist/handlers/chatHandler.js +464 -138
- package/dist/handlers/translateDocumentHandler.d.ts +14 -0
- package/dist/handlers/translateDocumentHandler.js +186 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +75 -6
- package/dist/payload/documentTranslation.d.ts +15 -0
- package/dist/payload/documentTranslation.js +120 -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 +2 -2
package/README.md
CHANGED
|
@@ -25,18 +25,18 @@ npm add @mvriu5/payload-ai @openrouter/ai-sdk-provider
|
|
|
25
25
|
## Usage
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
|
-
import { buildConfig } from "payload"
|
|
29
|
-
import { payloadAiPlugin } from "@mvriu5/payload-ai"
|
|
28
|
+
import { buildConfig } from "payload"
|
|
29
|
+
import { payloadAiPlugin } from "@mvriu5/payload-ai"
|
|
30
30
|
|
|
31
31
|
export default buildConfig({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
})
|
|
32
|
+
plugins: [
|
|
33
|
+
payloadAiPlugin({
|
|
34
|
+
collections: {
|
|
35
|
+
posts: true,
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
],
|
|
39
|
+
})
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Without centrally configured providers, the plugin adds two fields to the configured Payload admin user collection:
|
|
@@ -51,62 +51,72 @@ When `providers` is configured, provider selection and API keys are managed cent
|
|
|
51
51
|
## Options
|
|
52
52
|
|
|
53
53
|
```ts
|
|
54
|
-
import type { PayloadAIPluginOptions } from "@mvriu5/payload-ai"
|
|
54
|
+
import type { PayloadAIPluginOptions } from "@mvriu5/payload-ai"
|
|
55
55
|
|
|
56
56
|
const options: PayloadAIPluginOptions = {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
update: true,
|
|
57
|
+
aiInput: true,
|
|
58
|
+
allowUserApiKeys: false,
|
|
59
|
+
authCollections: {
|
|
60
|
+
aiInput: false,
|
|
61
|
+
generateFields: false,
|
|
63
62
|
},
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
collections: {
|
|
64
|
+
media: {
|
|
65
|
+
read: true,
|
|
66
|
+
update: true,
|
|
67
|
+
},
|
|
68
|
+
posts: {
|
|
69
|
+
read: true,
|
|
70
|
+
create: true,
|
|
71
|
+
update: true,
|
|
72
|
+
delete: false,
|
|
73
|
+
},
|
|
74
|
+
users: true,
|
|
75
|
+
},
|
|
76
|
+
generateFields: true,
|
|
77
|
+
media: {
|
|
78
|
+
enabled: true,
|
|
79
|
+
collectionSlug: "media",
|
|
80
|
+
acceptedMimeTypes: ["image/*"],
|
|
81
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
69
82
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
providers: [
|
|
84
|
+
{
|
|
85
|
+
id: "company-openai",
|
|
86
|
+
label: "Company OpenAI",
|
|
87
|
+
provider: "openai",
|
|
88
|
+
apiKey: process.env.COMPANY_OPENAI_API_KEY,
|
|
89
|
+
models: [
|
|
90
|
+
{ label: "GPT-4.1 Mini", value: "gpt-4.1-mini" },
|
|
91
|
+
{ label: "GPT-4.1", value: "gpt-4.1" },
|
|
92
|
+
],
|
|
93
|
+
defaultModel: "gpt-4.1-mini",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "ollama",
|
|
97
|
+
label: "Local Ollama",
|
|
98
|
+
provider: "openai",
|
|
99
|
+
baseURL: "http://localhost:11434/v1",
|
|
100
|
+
apiKey: "ollama",
|
|
101
|
+
models: [
|
|
102
|
+
{ label: "Llama 3.3", value: "llama3.3" },
|
|
103
|
+
{ label: "Qwen 3", value: "qwen3" },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
maxOutputTokens: 1200,
|
|
108
|
+
promptCaching: true,
|
|
109
|
+
translate: true,
|
|
110
|
+
uploadCollections: {
|
|
111
|
+
aiInput: true,
|
|
112
|
+
generateFields: false,
|
|
90
113
|
},
|
|
91
|
-
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
baseURL: "http://localhost:11434/v1",
|
|
96
|
-
apiKey: "ollama",
|
|
97
|
-
models: [
|
|
98
|
-
{ label: "Llama 3.3", value: "llama3.3" },
|
|
99
|
-
{ label: "Qwen 3", value: "qwen3" },
|
|
100
|
-
],
|
|
114
|
+
maxTokenUsage: {
|
|
115
|
+
type: "user",
|
|
116
|
+
perDay: 50_000,
|
|
117
|
+
perWeek: 250_000,
|
|
101
118
|
},
|
|
102
|
-
|
|
103
|
-
maxOutputTokens: 1200,
|
|
104
|
-
maxTokenUsage: {
|
|
105
|
-
type: "user",
|
|
106
|
-
perDay: 50_000,
|
|
107
|
-
perWeek: 250_000,
|
|
108
|
-
},
|
|
109
|
-
};
|
|
119
|
+
}
|
|
110
120
|
```
|
|
111
121
|
|
|
112
122
|
`models` configures model choices for the user-selected provider mode. Use `providers` instead when provider selection, credentials, and endpoints should be managed centrally.
|
|
@@ -117,7 +127,7 @@ Controls whether the collapsible AI Assistant input is added to collection and g
|
|
|
117
127
|
|
|
118
128
|
```ts
|
|
119
129
|
payloadAiPlugin({
|
|
120
|
-
|
|
130
|
+
aiInput: false,
|
|
121
131
|
})
|
|
122
132
|
```
|
|
123
133
|
|
|
@@ -129,12 +139,51 @@ Controls whether supported fields receive a Generate control. It defaults to `tr
|
|
|
129
139
|
|
|
130
140
|
```ts
|
|
131
141
|
payloadAiPlugin({
|
|
132
|
-
|
|
142
|
+
generateFields: false,
|
|
133
143
|
})
|
|
134
144
|
```
|
|
135
145
|
|
|
136
146
|
Generate controls are added to `text`, `textarea`, `richText`, and `json` fields in collection and global edit views. When disabled, the plugin also omits the `/ai-generate-field` endpoint. The embedded AI Assistant and dashboard remain available.
|
|
137
147
|
|
|
148
|
+
### `authCollections` and `uploadCollections`
|
|
149
|
+
|
|
150
|
+
Auth and upload collections do not receive the embedded AI Assistant or Generate controls by default. Enable either feature explicitly for the respective collection type:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
payloadAiPlugin({
|
|
154
|
+
authCollections: {
|
|
155
|
+
aiInput: true,
|
|
156
|
+
generateFields: true,
|
|
157
|
+
},
|
|
158
|
+
uploadCollections: {
|
|
159
|
+
aiInput: true,
|
|
160
|
+
generateFields: true,
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The global `aiInput` and `generateFields` options still take precedence when set to `false`.
|
|
166
|
+
|
|
167
|
+
### `translate`
|
|
168
|
+
|
|
169
|
+
Controls document translation for localized collections and globals. It defaults to `true`.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
payloadAiPlugin({
|
|
173
|
+
translate: false,
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
When enabled, the plugin adds a `Translate` action to the document header. It is shown only when:
|
|
178
|
+
|
|
179
|
+
- a non-default locale is selected
|
|
180
|
+
- at least one localized field is empty in that locale
|
|
181
|
+
- the corresponding field in the default locale contains source content
|
|
182
|
+
|
|
183
|
+
Translation fills only empty localized fields in the current Payload form and marks it as modified. Existing translated values are never overwritten. It does not save the document or global, so the generated values can be reviewed and validated before saving.
|
|
184
|
+
|
|
185
|
+
When disabled, the plugin does not register the header control or the `/ai-translate-document` endpoint.
|
|
186
|
+
|
|
138
187
|
### `collections`
|
|
139
188
|
|
|
140
189
|
Restricts AI read and write proposals to enabled collection slugs. If omitted, all non-internal Payload collections are available.
|
|
@@ -143,9 +192,9 @@ Use `true` to enable all AI actions for a collection:
|
|
|
143
192
|
|
|
144
193
|
```ts
|
|
145
194
|
payloadAiPlugin({
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
195
|
+
collections: {
|
|
196
|
+
posts: true,
|
|
197
|
+
},
|
|
149
198
|
})
|
|
150
199
|
```
|
|
151
200
|
|
|
@@ -153,14 +202,14 @@ Use granular permissions to control each action:
|
|
|
153
202
|
|
|
154
203
|
```ts
|
|
155
204
|
payloadAiPlugin({
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
205
|
+
collections: {
|
|
206
|
+
posts: {
|
|
207
|
+
read: true,
|
|
208
|
+
create: true,
|
|
209
|
+
update: true,
|
|
210
|
+
delete: false,
|
|
211
|
+
},
|
|
162
212
|
},
|
|
163
|
-
},
|
|
164
213
|
})
|
|
165
214
|
```
|
|
166
215
|
|
|
@@ -168,14 +217,14 @@ You can mix both forms in the same object:
|
|
|
168
217
|
|
|
169
218
|
```ts
|
|
170
219
|
payloadAiPlugin({
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
220
|
+
collections: {
|
|
221
|
+
posts: true,
|
|
222
|
+
pages: true,
|
|
223
|
+
users: {
|
|
224
|
+
read: true,
|
|
225
|
+
update: true,
|
|
226
|
+
},
|
|
177
227
|
},
|
|
178
|
-
},
|
|
179
228
|
})
|
|
180
229
|
```
|
|
181
230
|
|
|
@@ -187,19 +236,19 @@ Enables media uploads from the AI assistant. Uploaded files are created through
|
|
|
187
236
|
|
|
188
237
|
```ts
|
|
189
238
|
payloadAiPlugin({
|
|
190
|
-
|
|
239
|
+
collections: {
|
|
240
|
+
media: {
|
|
241
|
+
read: true,
|
|
242
|
+
update: true,
|
|
243
|
+
},
|
|
244
|
+
posts: true,
|
|
245
|
+
},
|
|
191
246
|
media: {
|
|
192
|
-
|
|
193
|
-
|
|
247
|
+
enabled: true,
|
|
248
|
+
collectionSlug: "media",
|
|
249
|
+
acceptedMimeTypes: ["image/*"],
|
|
250
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
194
251
|
},
|
|
195
|
-
posts: true,
|
|
196
|
-
},
|
|
197
|
-
media: {
|
|
198
|
-
enabled: true,
|
|
199
|
-
collectionSlug: "media",
|
|
200
|
-
acceptedMimeTypes: ["image/*"],
|
|
201
|
-
maxFileSize: 10 * 1024 * 1024,
|
|
202
|
-
},
|
|
203
252
|
})
|
|
204
253
|
```
|
|
205
254
|
|
|
@@ -254,7 +303,7 @@ Controls the maximum number of output tokens the chat endpoint may generate per
|
|
|
254
303
|
|
|
255
304
|
```ts
|
|
256
305
|
payloadAiPlugin({
|
|
257
|
-
|
|
306
|
+
maxOutputTokens: 1200,
|
|
258
307
|
})
|
|
259
308
|
```
|
|
260
309
|
|
|
@@ -264,11 +313,11 @@ Limits total AI tokens across rolling 24-hour and 7-day windows.
|
|
|
264
313
|
|
|
265
314
|
```ts
|
|
266
315
|
payloadAiPlugin({
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
316
|
+
maxTokenUsage: {
|
|
317
|
+
type: "user",
|
|
318
|
+
perDay: 50_000,
|
|
319
|
+
perWeek: 250_000,
|
|
320
|
+
},
|
|
272
321
|
})
|
|
273
322
|
```
|
|
274
323
|
|
|
@@ -276,13 +325,25 @@ Use `type: "user"` to enforce separate budgets per authenticated user, or `type:
|
|
|
276
325
|
|
|
277
326
|
Completed model usage is stored in the hidden `payload-ai-usage` collection. Requests made after a limit is reached return HTTP `429`. Because providers report token usage after completion, the request that crosses a limit is allowed to finish and subsequent requests are blocked.
|
|
278
327
|
|
|
328
|
+
### `promptCaching`
|
|
329
|
+
|
|
330
|
+
Enables provider prompt-caching hints and defaults to `true`. The chat endpoint keeps static instructions and Payload schemas in a stable prompt prefix, adds Anthropic cache-control breakpoints, and supplies a stable OpenAI prompt cache key. Gemini uses the same stable prefix with its implicit caching.
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
payloadAiPlugin({
|
|
334
|
+
promptCaching: false,
|
|
335
|
+
})
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Disable it when explicit provider-side caching is not desired. Dynamic document data and user prompts are never placed in the cacheable schema section. Managed providers with a custom `baseURL` receive the stable prefix but no provider-specific cache parameters, preserving compatibility with Ollama, vLLM, and other OpenAI-compatible endpoints.
|
|
339
|
+
|
|
279
340
|
### `allowUserApiKeys`
|
|
280
341
|
|
|
281
342
|
Controls whether the plugin adds an `aiApiKey` field to the admin user collection.
|
|
282
343
|
|
|
283
344
|
```ts
|
|
284
345
|
payloadAiPlugin({
|
|
285
|
-
|
|
346
|
+
allowUserApiKeys: false,
|
|
286
347
|
})
|
|
287
348
|
```
|
|
288
349
|
|
|
@@ -322,12 +383,12 @@ The apply endpoint returns only minimal status/doc references and does not retur
|
|
|
322
383
|
## Exports
|
|
323
384
|
|
|
324
385
|
```ts
|
|
325
|
-
import { payloadAiPlugin } from "@mvriu5/payload-ai"
|
|
326
|
-
import type { PayloadAiPluginOptions } from "@mvriu5/payload-ai"
|
|
386
|
+
import { payloadAiPlugin } from "@mvriu5/payload-ai"
|
|
387
|
+
import type { PayloadAiPluginOptions } from "@mvriu5/payload-ai"
|
|
327
388
|
```
|
|
328
389
|
|
|
329
390
|
Client components are exported through:
|
|
330
391
|
|
|
331
392
|
```ts
|
|
332
|
-
import { AIInput, AIApiKeyField } from "@mvriu5/payload-ai/client"
|
|
393
|
+
import { AIInput, AIApiKeyField } from "@mvriu5/payload-ai/client"
|
|
333
394
|
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SystemModelMessage } from "ai";
|
|
2
|
+
import type { AIProvider } from "./providerOptions.js";
|
|
3
|
+
type PromptCacheContext = {
|
|
4
|
+
cacheable: Record<string, unknown>[];
|
|
5
|
+
dynamic: Record<string, unknown>[];
|
|
6
|
+
};
|
|
7
|
+
type PromptCacheProviderOptions = NonNullable<SystemModelMessage["providerOptions"]>;
|
|
8
|
+
export declare const splitPromptCacheContext: (mentionContext: Record<string, unknown>[]) => PromptCacheContext;
|
|
9
|
+
export declare const createCachedSystemMessages: ({ cacheableContext, cacheableInstructions, dynamicInstructions, enabled, provider, staticInstructions, }: {
|
|
10
|
+
cacheableContext: Record<string, unknown>[];
|
|
11
|
+
cacheableInstructions: string[];
|
|
12
|
+
dynamicInstructions: string[];
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
provider: AIProvider;
|
|
15
|
+
staticInstructions: string[];
|
|
16
|
+
}) => SystemModelMessage[];
|
|
17
|
+
export declare const createPromptCacheProviderOptions: ({ cacheKeyParts, enabled, model, provider, }: {
|
|
18
|
+
cacheKeyParts: unknown[];
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
model: string;
|
|
21
|
+
provider: AIProvider;
|
|
22
|
+
}) => PromptCacheProviderOptions | undefined;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const getContextKey = (context)=>{
|
|
3
|
+
return [
|
|
4
|
+
context.type,
|
|
5
|
+
context.parent,
|
|
6
|
+
context.collection,
|
|
7
|
+
context.slug
|
|
8
|
+
].filter(Boolean).join(":") || JSON.stringify(context);
|
|
9
|
+
};
|
|
10
|
+
export const splitPromptCacheContext = (mentionContext)=>{
|
|
11
|
+
const cacheable = [];
|
|
12
|
+
const dynamic = [];
|
|
13
|
+
const cacheableKeys = new Set();
|
|
14
|
+
const addCacheable = (context)=>{
|
|
15
|
+
const key = getContextKey(context);
|
|
16
|
+
if (cacheableKeys.has(key)) return;
|
|
17
|
+
cacheableKeys.add(key);
|
|
18
|
+
cacheable.push(context);
|
|
19
|
+
};
|
|
20
|
+
for (const context of mentionContext){
|
|
21
|
+
if ([
|
|
22
|
+
"block",
|
|
23
|
+
"collection"
|
|
24
|
+
].includes(String(context.type))) {
|
|
25
|
+
addCacheable(context);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (context.type === "global" && "doc" in context) {
|
|
29
|
+
const { doc, ...schema } = context;
|
|
30
|
+
addCacheable(schema);
|
|
31
|
+
dynamic.push({
|
|
32
|
+
doc,
|
|
33
|
+
slug: context.slug,
|
|
34
|
+
type: "globalDocument"
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (context.type === "global") {
|
|
39
|
+
addCacheable(context);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (context.type === "mediaAttachment" && context.schema && typeof context.schema === "object") {
|
|
43
|
+
addCacheable(context.schema);
|
|
44
|
+
const { schema: _schema, ...attachmentContext } = context;
|
|
45
|
+
dynamic.push(attachmentContext);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
dynamic.push(context);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
cacheable,
|
|
52
|
+
dynamic
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const withAnthropicCacheControl = (message, enabled, provider)=>{
|
|
56
|
+
if (!enabled || provider !== "claude") return message;
|
|
57
|
+
return {
|
|
58
|
+
...message,
|
|
59
|
+
providerOptions: {
|
|
60
|
+
anthropic: {
|
|
61
|
+
cacheControl: {
|
|
62
|
+
type: "ephemeral"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
export const createCachedSystemMessages = ({ cacheableContext, cacheableInstructions, dynamicInstructions, enabled, provider, staticInstructions })=>{
|
|
69
|
+
const messages = [
|
|
70
|
+
withAnthropicCacheControl({
|
|
71
|
+
content: staticInstructions.join("\n"),
|
|
72
|
+
role: "system"
|
|
73
|
+
}, enabled, provider)
|
|
74
|
+
];
|
|
75
|
+
const scopedContent = [
|
|
76
|
+
...cacheableContext.length ? [
|
|
77
|
+
"Payload schema references JSON. Use exact field names and values:",
|
|
78
|
+
JSON.stringify(cacheableContext)
|
|
79
|
+
] : [],
|
|
80
|
+
...cacheableInstructions
|
|
81
|
+
];
|
|
82
|
+
if (scopedContent.length > 0) {
|
|
83
|
+
messages.push(withAnthropicCacheControl({
|
|
84
|
+
content: scopedContent.join("\n"),
|
|
85
|
+
role: "system"
|
|
86
|
+
}, enabled, provider));
|
|
87
|
+
}
|
|
88
|
+
if (dynamicInstructions.length > 0) {
|
|
89
|
+
messages.push({
|
|
90
|
+
content: dynamicInstructions.join("\n"),
|
|
91
|
+
role: "system"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return messages;
|
|
95
|
+
};
|
|
96
|
+
export const createPromptCacheProviderOptions = ({ cacheKeyParts, enabled, model, provider })=>{
|
|
97
|
+
if (!enabled) return undefined;
|
|
98
|
+
if (provider === "openai") {
|
|
99
|
+
const digest = createHash("sha256").update(JSON.stringify([
|
|
100
|
+
"payload-ai-v1",
|
|
101
|
+
model,
|
|
102
|
+
...cacheKeyParts
|
|
103
|
+
])).digest("hex").slice(0, 32);
|
|
104
|
+
return {
|
|
105
|
+
openai: {
|
|
106
|
+
promptCacheKey: `payload-ai-${digest}`
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (provider === "openrouter" && model.toLowerCase().startsWith("anthropic/")) {
|
|
111
|
+
return {
|
|
112
|
+
openrouter: {
|
|
113
|
+
cache_control: {
|
|
114
|
+
type: "ephemeral"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ProposalValidationIssue } from "../payload/proposalData.js";
|
|
2
|
+
export declare const maxProposalRepairAttempts = 1;
|
|
3
|
+
export type CompactProposalRepairIssue = {
|
|
4
|
+
code: string;
|
|
5
|
+
hint: string;
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
type ProposalRepairTarget = {
|
|
9
|
+
collection?: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
slug?: string;
|
|
12
|
+
tool: string;
|
|
13
|
+
};
|
|
14
|
+
export declare const compactProposalRepairIssues: (issues: ProposalValidationIssue[]) => CompactProposalRepairIssue[];
|
|
15
|
+
export declare const createProposalRepairKey: ({ collection, id, slug, tool }: ProposalRepairTarget) => string;
|
|
16
|
+
export declare const createProposalRepairTracker: () => {
|
|
17
|
+
beginCall(target: ProposalRepairTarget): "initial" | "blocked" | "repair";
|
|
18
|
+
registerFailure(target: ProposalRepairTarget): {
|
|
19
|
+
attempt: number;
|
|
20
|
+
errorCode: "REPAIR_EXHAUSTED";
|
|
21
|
+
maxAttempts: number;
|
|
22
|
+
retryable: boolean;
|
|
23
|
+
} | {
|
|
24
|
+
attempt: number;
|
|
25
|
+
errorCode: "INVALID_PROPOSAL_DATA";
|
|
26
|
+
maxAttempts: number;
|
|
27
|
+
retryable: boolean;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const maxProposalRepairAttempts = 1;
|
|
2
|
+
const getIssueHint = (issue)=>{
|
|
3
|
+
switch(issue.code){
|
|
4
|
+
case "invalid_array":
|
|
5
|
+
return "Use an array of complete objects matching the child fields.";
|
|
6
|
+
case "invalid_block_type":
|
|
7
|
+
return "Use an exact blockType from the schema.";
|
|
8
|
+
case "invalid_blocks":
|
|
9
|
+
return "Use an array of block objects with blockType and exact field names.";
|
|
10
|
+
case "invalid_checkbox":
|
|
11
|
+
return "Use true or false.";
|
|
12
|
+
case "invalid_container":
|
|
13
|
+
return "Use an object matching the nested field schema.";
|
|
14
|
+
case "invalid_date":
|
|
15
|
+
return "Use a valid date string.";
|
|
16
|
+
case "invalid_option":
|
|
17
|
+
return issue.message;
|
|
18
|
+
case "invalid_relationship":
|
|
19
|
+
return "Use an existing document ID or { relationTo, value } for polymorphic relationships.";
|
|
20
|
+
case "missing_required_field":
|
|
21
|
+
return "Add the required field with a schema-compatible value.";
|
|
22
|
+
case "non_localized_field_in_secondary_locale":
|
|
23
|
+
return "Move this field to the primary locale data.";
|
|
24
|
+
case "unknown_field":
|
|
25
|
+
return "Remove it and use exact schema field names only.";
|
|
26
|
+
default:
|
|
27
|
+
return issue.message;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export const compactProposalRepairIssues = (issues)=>{
|
|
31
|
+
return issues.slice(0, 6).map((issue)=>({
|
|
32
|
+
code: issue.code,
|
|
33
|
+
hint: getIssueHint(issue).slice(0, 180),
|
|
34
|
+
path: issue.path
|
|
35
|
+
}));
|
|
36
|
+
};
|
|
37
|
+
export const createProposalRepairKey = ({ collection, id, slug, tool })=>{
|
|
38
|
+
return [
|
|
39
|
+
tool,
|
|
40
|
+
collection || slug || "",
|
|
41
|
+
id || ""
|
|
42
|
+
].join(":");
|
|
43
|
+
};
|
|
44
|
+
export const createProposalRepairTracker = ()=>{
|
|
45
|
+
const statesByTarget = new Map();
|
|
46
|
+
return {
|
|
47
|
+
beginCall (target) {
|
|
48
|
+
const state = statesByTarget.get(createProposalRepairKey(target));
|
|
49
|
+
if (!state) return "initial";
|
|
50
|
+
if (state.repairCallConsumed) return "blocked";
|
|
51
|
+
state.repairCallConsumed = true;
|
|
52
|
+
return "repair";
|
|
53
|
+
},
|
|
54
|
+
registerFailure (target) {
|
|
55
|
+
const key = createProposalRepairKey(target);
|
|
56
|
+
const state = statesByTarget.get(key);
|
|
57
|
+
if (state) {
|
|
58
|
+
return {
|
|
59
|
+
attempt: maxProposalRepairAttempts,
|
|
60
|
+
errorCode: "REPAIR_EXHAUSTED",
|
|
61
|
+
maxAttempts: maxProposalRepairAttempts,
|
|
62
|
+
retryable: false
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
statesByTarget.set(key, {
|
|
66
|
+
repairCallConsumed: false
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
attempt: 1,
|
|
70
|
+
errorCode: "INVALID_PROPOSAL_DATA",
|
|
71
|
+
maxAttempts: maxProposalRepairAttempts,
|
|
72
|
+
retryable: true
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
};
|
package/dist/ai/sensitiveData.js
CHANGED
|
@@ -5,10 +5,12 @@ const sensitiveKeyPatterns = [
|
|
|
5
5
|
/^aiApiKey$/i,
|
|
6
6
|
/^authorization$/i,
|
|
7
7
|
/^accessToken$/i,
|
|
8
|
+
/^password$/i,
|
|
9
|
+
/^privateKey$/i,
|
|
8
10
|
/^refreshToken$/i,
|
|
9
11
|
/^secret$/i
|
|
10
12
|
];
|
|
11
|
-
const isSensitiveKey = (key)=>sensitiveKeyPatterns.some((pattern)=>pattern.test(key));
|
|
13
|
+
export const isSensitiveKey = (key)=>sensitiveKeyPatterns.some((pattern)=>pattern.test(key));
|
|
12
14
|
export const containsSensitiveData = (value)=>{
|
|
13
15
|
if (Array.isArray(value)) return value.some(containsSensitiveData);
|
|
14
16
|
if (!isRecord(value)) return false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { Button, ChevronIcon, useConfig, useDocumentForm, useDocumentInfo, useLocale } from "@payloadcms/ui";
|
|
3
|
+
import { Button, ChevronIcon, useAuth, useConfig, useDocumentForm, useDocumentInfo, useLocale } from "@payloadcms/ui";
|
|
4
4
|
import { formatAdminURL } from "payload/shared";
|
|
5
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
6
|
import { mergeData } from "../../payload/shared.js";
|
|
@@ -540,6 +540,8 @@ const DocumentAIInput = ()=>{
|
|
|
540
540
|
});
|
|
541
541
|
};
|
|
542
542
|
const AIInput = ({ isDashboard = false })=>{
|
|
543
|
+
const { user } = useAuth();
|
|
544
|
+
if (!user) return null;
|
|
543
545
|
if (isDashboard) return /*#__PURE__*/ _jsx(AIInputCore, {
|
|
544
546
|
isDashboard: true
|
|
545
547
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useConfig, useDocumentForm, useDocumentInfo, useField, useLocale } from "@payloadcms/ui";
|
|
3
|
+
import { useAuth, useConfig, useDocumentForm, useDocumentInfo, useField, useLocale } from "@payloadcms/ui";
|
|
4
4
|
import { formatAdminURL } from "payload/shared";
|
|
5
5
|
import { useState } from "react";
|
|
6
6
|
import { createGeneratedRichTextValue } from "./richText.js";
|
|
@@ -27,6 +27,7 @@ const requestGeneratedValue = ({ apiRoute, body, cacheKey })=>{
|
|
|
27
27
|
return request;
|
|
28
28
|
};
|
|
29
29
|
const GenerateField = ({ field, generationFieldKey, generationFieldType, path, readOnly })=>{
|
|
30
|
+
const { user } = useAuth();
|
|
30
31
|
const configContext = useConfig();
|
|
31
32
|
const documentForm = useDocumentForm();
|
|
32
33
|
const documentInfo = useDocumentInfo();
|
|
@@ -87,7 +88,7 @@ const GenerateField = ({ field, generationFieldKey, generationFieldType, path, r
|
|
|
87
88
|
setIsGenerating(false);
|
|
88
89
|
}
|
|
89
90
|
};
|
|
90
|
-
if (!scope) return null;
|
|
91
|
+
if (!user || !scope) return null;
|
|
91
92
|
return /*#__PURE__*/ _jsxs("div", {
|
|
92
93
|
className: styles.generateField,
|
|
93
94
|
children: [
|