@simmalugnt-se/payload-editor-assistant 0.1.0 → 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ - Provider failures are logged on the server through Payload's logger (missing model, `ai` not
6
+ installed, or the provider's own error), instead of only showing `provider_error` in the drawer.
7
+ - README: how to connect the provider's API key, what the assistant may do, and the options.
8
+
9
+ ## 0.1.0
10
+
11
+ - First release.
12
+ - `ai` is a required peer: the AI SDK is loaded with a literal import so bundlers can resolve it.
package/README.md CHANGED
@@ -9,24 +9,84 @@ your choice.
9
9
 
10
10
  ## Setup
11
11
 
12
- ```ts
13
- import { deepSeek } from "@ai-sdk/deepseek";
14
- import { editorAssistantPlugin } from "@simmalugnt-se/payload-editor-assistant";
15
-
16
- plugins: [
17
- editorAssistantPlugin({
18
- model: deepSeek("deepseek-chat"),
19
- collections: {
20
- pages: {
21
- capabilities: ["content.find", "content.findById", "form.read", "form.propose", "draft.create"],
22
- },
23
- },
24
- globals: {
25
- header: { capabilities: ["content.findById", "form.read", "form.propose"] },
26
- },
27
- }),
28
- ];
29
- ```
30
-
31
- Then regenerate the import map (`pnpm payload generate:importmap`). See `EditorAssistantPluginOptions`
32
- for allowlists, host tools and instructions. Design notes live in `docs/research` and `.wayfinder`.
12
+ 1. Install the plugin, the AI SDK and a provider (DeepSeek here; any AI SDK provider works):
13
+
14
+ ```bash
15
+ pnpm add @simmalugnt-se/payload-editor-assistant ai @ai-sdk/deepseek
16
+ ```
17
+
18
+ 2. Put the provider's API key in the server environment, e.g. `.env.local` locally and the
19
+ hosting provider's environment variables in production. Never expose it to the browser
20
+ (no `NEXT_PUBLIC_` prefix):
21
+
22
+ ```bash
23
+ DEEPSEEK_API_KEY=sk-...
24
+ ```
25
+
26
+ 3. Create the model with that key and pass it to the plugin:
27
+
28
+ ```ts
29
+ import { createDeepSeek } from "@ai-sdk/deepseek";
30
+ import { editorAssistantPlugin } from "@simmalugnt-se/payload-editor-assistant";
31
+
32
+ const deepSeek = createDeepSeek({ apiKey: process.env.DEEPSEEK_API_KEY });
33
+
34
+ plugins: [
35
+ editorAssistantPlugin({
36
+ model: deepSeek("deepseek-chat"),
37
+ collections: {
38
+ pages: {
39
+ capabilities: ["content.find", "content.findById", "form.read", "form.propose", "draft.create"],
40
+ },
41
+ },
42
+ globals: {
43
+ header: { capabilities: ["content.findById", "form.read", "form.propose"] },
44
+ },
45
+ }),
46
+ ];
47
+ ```
48
+
49
+ 4. Regenerate the import map: `pnpm payload generate:importmap`.
50
+
51
+ ## API keys
52
+
53
+ The plugin has no provider or key of its own: it only receives the `model` you create, and the model
54
+ is called on the server. To switch provider, install another AI SDK provider and create the model
55
+ with its key, e.g. `createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY })`.
56
+
57
+ Providers also read a default environment variable (`DEEPSEEK_API_KEY`, `ANTHROPIC_API_KEY`, ...)
58
+ when no `apiKey` is passed, but passing it explicitly keeps the connection visible in your config.
59
+
60
+ If the key is missing or wrong, the drawer shows `provider_error` and the cause is logged on the
61
+ server through Payload's logger.
62
+
63
+ ## What the assistant may do
64
+
65
+ Nothing is reachable unless you list it. Each collection or global gets the capabilities it allows:
66
+
67
+ | Capability | What it does | Needs approval |
68
+ |---|---|---|
69
+ | `content.find` | search and list documents | no |
70
+ | `content.findById` | read one document | no |
71
+ | `form.read` | read the open edit form, including unsaved changes | no |
72
+ | `form.propose` | propose changes to the open form; the editor reviews them before they apply | yes |
73
+ | `draft.create` | create a new draft document (collections only) | yes |
74
+
75
+ The assistant never publishes. Approved form changes land in the open form and the editor saves as
76
+ usual; an approved `draft.create` saves a new, unpublished draft. The `users` collection, Payload's own collections and fields that look like
77
+ secrets (`password`, `token`, `apiKey`, ...) are always off limits.
78
+
79
+ ## Options
80
+
81
+ | Option | |
82
+ |---|---|
83
+ | `model` | The AI SDK model, or `({ userId }) => model` to choose per user. Required unless `disabled`. |
84
+ | `collections`, `globals` | `{ [slug]: { capabilities, fields? } }`. `fields` narrows what is visible: `{ include: [...] }` or `{ exclude: [...] }`. |
85
+ | `denyFields` | Field names hidden from the assistant in every collection and global. |
86
+ | `instructions` | Project rules the assistant cannot infer from the schema, e.g. "the start page has slug `home`". |
87
+ | `shortcuts` | One-click prompts in the drawer: `{ id, label, prompt, when?, entities? }`. |
88
+ | `enabled` | `({ userId }) => boolean` to offer the assistant to some users only. |
89
+ | `redact` | `({ slug, data }) => data` to strip values before documents reach the model. |
90
+ | `trustedOrigins` | Origins allowed to call the assistant's endpoints. Default: only the origin serving Admin. When set, only the listed origins. |
91
+ | `audit.collection` | Slug of the collection that logs assistant actions (default `editor-assistant-audit`). |
92
+ | `disabled` | Turn the plugin off without removing its config. |
@@ -1,3 +1,4 @@
1
+ import { PACKAGE_NAME } from "../package-name.js";
1
2
  import { loadTurnContext } from "./context.js";
2
3
  import { needsProposeRetry } from "./propose-loop.js";
3
4
  import { buildSystemPrompt } from "./system-prompt.js";
@@ -9,10 +10,12 @@ const TIMEOUT_MS = 60_000;
9
10
  export async function runAgentTurn(req, options, input) {
10
11
  const model = await resolveModel(options, req.user?.id);
11
12
  if (!model) {
13
+ logProviderError(req, "no model: the `model` option resolved to nothing for this user");
12
14
  return { ok: false, error: "provider_error" };
13
15
  }
14
16
  const ai = await loadAiSdk();
15
17
  if (!ai) {
18
+ logProviderError(req, "could not load the AI SDK: is `ai` installed in the project?");
16
19
  return { ok: false, error: "provider_error" };
17
20
  }
18
21
  const ctx = {
@@ -39,6 +42,7 @@ export async function runAgentTurn(req, options, input) {
39
42
  signal,
40
43
  });
41
44
  if (!first) {
45
+ logProviderError(req, "the model returned no result");
42
46
  return { ok: false, error: "provider_error" };
43
47
  }
44
48
  const collected = collectResult(String(first.text ?? ""), first, captured, input.bridge.language ?? input.bridge.locale);
@@ -79,6 +83,7 @@ export async function runAgentTurn(req, options, input) {
79
83
  if (signal.aborted) {
80
84
  return { ok: false, error: "timeout" };
81
85
  }
86
+ logProviderError(req, "the model provider failed", error);
82
87
  return {
83
88
  ok: false,
84
89
  error: "provider_error",
@@ -246,6 +251,13 @@ async function resolveModel(options, userId) {
246
251
  }
247
252
  return options.model;
248
253
  }
254
+ /**
255
+ * The drawer only shows "provider_error"; the cause (missing key, wrong model id, provider outage)
256
+ * belongs in the server log where the host can see it.
257
+ */
258
+ function logProviderError(req, message, error) {
259
+ req.payload?.logger?.error({ err: error, msg: `${PACKAGE_NAME}: ${message}` });
260
+ }
249
261
  async function loadAiSdk() {
250
262
  try {
251
263
  // A literal specifier: bundlers only resolve `import()` they can read, and the host's `ai`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simmalugnt-se/payload-editor-assistant",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Context-aware editor assistant plugin for Payload Admin",
5
5
  "keywords": [
6
6
  "payload",
@@ -18,7 +18,8 @@
18
18
  "node": ">=22"
19
19
  },
20
20
  "files": [
21
- "dist"
21
+ "dist",
22
+ "CHANGELOG.md"
22
23
  ],
23
24
  "sideEffects": [
24
25
  "*.css"