strapi-plugin-ai-sdk 0.7.7 → 0.8.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.
Files changed (32) hide show
  1. package/dist/_chunks/{App-joEmdpxi.js → App-DTbAnq7K.js} +710 -51
  2. package/dist/_chunks/{App-DMdQymB3.mjs → App-DZVBmpvs.mjs} +712 -53
  3. package/dist/_chunks/{index-B7qLITWV.js → index-C3xn2IND.js} +1 -1
  4. package/dist/_chunks/{index-DrLcqX__.mjs → index-Ca2F2wBp.mjs} +1 -1
  5. package/dist/admin/index.js +1 -1
  6. package/dist/admin/index.mjs +1 -1
  7. package/dist/admin/src/components/NotePanel.d.ts +15 -0
  8. package/dist/admin/src/hooks/useNotes.d.ts +22 -0
  9. package/dist/admin/src/pages/NoteStorePage.d.ts +2 -0
  10. package/dist/admin/src/utils/notes-api.d.ts +28 -0
  11. package/dist/server/index.js +536 -121
  12. package/dist/server/index.mjs +536 -121
  13. package/dist/server/src/content-types/index.d.ts +46 -0
  14. package/dist/server/src/content-types/note/index.d.ts +47 -0
  15. package/dist/server/src/controllers/index.d.ts +9 -0
  16. package/dist/server/src/controllers/note.d.ts +12 -0
  17. package/dist/server/src/index.d.ts +55 -0
  18. package/dist/server/src/lib/trim-messages.d.ts +11 -0
  19. package/dist/server/src/mcp/resources/tool-guide.d.ts +5 -0
  20. package/dist/server/src/tool-logic/aggregate-content.d.ts +1 -1
  21. package/dist/server/src/tool-logic/create-content.d.ts +1 -1
  22. package/dist/server/src/tool-logic/find-one-content.d.ts +1 -1
  23. package/dist/server/src/tool-logic/index.d.ts +4 -0
  24. package/dist/server/src/tool-logic/list-content-types.d.ts +1 -1
  25. package/dist/server/src/tool-logic/manage-task.d.ts +1 -1
  26. package/dist/server/src/tool-logic/recall-notes.d.ts +22 -0
  27. package/dist/server/src/tool-logic/save-note.d.ts +17 -0
  28. package/dist/server/src/tool-logic/update-content.d.ts +1 -1
  29. package/dist/server/src/tool-logic/upload-media.d.ts +1 -1
  30. package/dist/server/src/tools/definitions/recall-notes.d.ts +2 -0
  31. package/dist/server/src/tools/definitions/save-note.d.ts +2 -0
  32. package/package.json +1 -1
@@ -30,6 +30,90 @@ function _interopNamespace(e) {
30
30
  }
31
31
  const fs__namespace = /* @__PURE__ */ _interopNamespace(fs$1);
32
32
  const path__namespace = /* @__PURE__ */ _interopNamespace(path$1);
33
+ function toSnakeCase$1(str) {
34
+ return str.replace(/:/g, "__").replace(/-/g, "_").replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
35
+ }
36
+ function extractType(field) {
37
+ const def = field?._zod?.def;
38
+ if (!def) return "unknown";
39
+ switch (def.type) {
40
+ case "string":
41
+ return "string";
42
+ case "number":
43
+ return "number";
44
+ case "boolean":
45
+ return "boolean";
46
+ case "enum":
47
+ return Object.keys(def.entries).join(" | ");
48
+ case "optional":
49
+ return extractType({ _zod: { def: def.innerType } });
50
+ case "default":
51
+ return extractType({ _zod: { def: def.innerType } });
52
+ case "record":
53
+ return "object";
54
+ case "array":
55
+ return "array";
56
+ case "union":
57
+ return "string | array | object";
58
+ default:
59
+ return def.type || "unknown";
60
+ }
61
+ }
62
+ function formatParams(schema2) {
63
+ const shape = schema2.shape;
64
+ if (!shape || Object.keys(shape).length === 0) {
65
+ return "_No parameters._\n";
66
+ }
67
+ const rows = [];
68
+ rows.push("| Parameter | Type | Required | Description |");
69
+ rows.push("|-----------|------|----------|-------------|");
70
+ for (const [name, field] of Object.entries(shape)) {
71
+ const required = !field.isOptional?.();
72
+ const type = extractType(field);
73
+ const desc = (field.description || "").replace(/\|/g, "\\|");
74
+ rows.push(`| ${name} | ${type} | ${required ? "Yes" : "No"} | ${desc} |`);
75
+ }
76
+ return rows.join("\n") + "\n";
77
+ }
78
+ function generateToolGuide(registry) {
79
+ const tools = registry.getPublic();
80
+ const sources = registry.getToolSources();
81
+ const sections = [];
82
+ sections.push("# Strapi AI Tools Guide\n");
83
+ sections.push("## Getting Started\n");
84
+ sections.push(
85
+ "Start with `list_content_types` to discover your content types and their fields, then use `search_content` to query them.\n"
86
+ );
87
+ for (const source of sources) {
88
+ const heading = source.id === "built-in" ? "Built-in Tools" : source.label;
89
+ sections.push(`## ${heading}
90
+ `);
91
+ for (const toolName of source.tools) {
92
+ const def = tools.get(toolName);
93
+ if (!def) continue;
94
+ const mcpName = toSnakeCase$1(toolName);
95
+ sections.push(`### ${mcpName}
96
+ `);
97
+ sections.push(`${def.description}
98
+ `);
99
+ sections.push(formatParams(def.schema));
100
+ }
101
+ }
102
+ sections.push("## Common Workflows\n");
103
+ sections.push("### Create a blog post\n");
104
+ sections.push(
105
+ "1. Call `list_content_types` to discover the article/blog content type and its fields\n2. (Optional) Call `upload_media` with a URL to upload a cover image\n3. Call `create_content` with the content type UID and field data\n"
106
+ );
107
+ sections.push("### Find and update content\n");
108
+ sections.push(
109
+ "1. Call `search_content` with filters or a query to find the document\n2. Note the `documentId` from the results\n3. Call `update_content` with the content type, documentId, and fields to change\n"
110
+ );
111
+ sections.push("### Content analytics\n");
112
+ sections.push(
113
+ "1. Call `aggregate_content` with operation `count` for totals\n2. Use `countByField` with `groupByField` to see distribution (e.g. articles per category)\n3. Use `countByDateRange` with `granularity` to see trends over time\n"
114
+ );
115
+ return sections.join("\n");
116
+ }
33
117
  function toSnakeCase(str) {
34
118
  return str.replace(/:/g, "__").replace(/-/g, "_").replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
35
119
  }
@@ -46,7 +130,8 @@ function createMcpServer(strapi) {
46
130
  },
47
131
  {
48
132
  capabilities: {
49
- tools: {}
133
+ tools: {},
134
+ resources: {}
50
135
  }
51
136
  }
52
137
  );
@@ -74,6 +159,24 @@ function createMcpServer(strapi) {
74
159
  }
75
160
  );
76
161
  }
162
+ const guideMarkdown = generateToolGuide(registry);
163
+ server.registerResource(
164
+ "Tool Guide",
165
+ "strapi://tools/guide",
166
+ {
167
+ description: "Complete guide to all available Strapi AI tools with parameters and usage examples",
168
+ mimeType: "text/markdown"
169
+ },
170
+ async () => ({
171
+ contents: [
172
+ {
173
+ uri: "strapi://tools/guide",
174
+ mimeType: "text/markdown",
175
+ text: guideMarkdown
176
+ }
177
+ ]
178
+ })
179
+ );
77
180
  strapi.log.info("[ai-sdk:mcp] MCP server created with tools:", { tools: toolNames });
78
181
  return server;
79
182
  }
@@ -239,7 +342,7 @@ class ToolRegistry {
239
342
  }
240
343
  }
241
344
  const listContentTypesSchema = zod.z.object({});
242
- const listContentTypesDescription = "List all Strapi content types and components with their fields, relations, and structure.";
345
+ const listContentTypesDescription = 'List all Strapi content types and components with their fields, relations, and structure. This is the starting point for any content operation — call it first to discover content type UIDs (e.g. "api::article.article"), field names, relation targets, and components. No parameters required. Results are cached.';
243
346
  const INTERNAL_FIELDS = /* @__PURE__ */ new Set([
244
347
  "createdAt",
245
348
  "updatedAt",
@@ -393,7 +496,7 @@ const createContentSchema = zod.z.object({
393
496
  status: zod.z.enum(["draft", "published"]).optional().describe("Document status. Defaults to draft."),
394
497
  locale: zod.z.string().optional().describe('Locale code for i18n content, e.g. "en" or "fr"')
395
498
  });
396
- const createContentDescription = "Create a new document in any Strapi content type (articles, blog posts, pages, etc.). Use listContentTypes first to discover available content types and their fields.";
499
+ const createContentDescription = "Create a new document in any Strapi content type. Requires contentType (UID) and data (field values). Call listContentTypes first to discover UIDs and required fields. To attach media, upload it first with uploadMedia, then pass the file ID in data. Returns the created document with all fields populated.";
397
500
  async function createContent(strapi, params) {
398
501
  const { contentType, data, status, locale } = params;
399
502
  if (!strapi.contentTypes[contentType]) {
@@ -415,7 +518,7 @@ const updateContentSchema = zod.z.object({
415
518
  status: zod.z.enum(["draft", "published"]).optional().describe("Document status. Defaults to draft."),
416
519
  locale: zod.z.string().optional().describe('Locale code for i18n content, e.g. "en" or "fr"')
417
520
  });
418
- const updateContentDescription = "Update an existing document in any Strapi content type. Use searchContent to find the document ID first.";
521
+ const updateContentDescription = "Update an existing document in any Strapi content type. Requires contentType, documentId, and data (fields to change — only include fields you want to modify). Typical workflow: searchContent to find the documentId, then updateContent to modify it. Returns the updated document.";
419
522
  async function updateContent(strapi, params) {
420
523
  const { contentType, documentId, data, status, locale } = params;
421
524
  if (!strapi.contentTypes[contentType]) {
@@ -488,7 +591,7 @@ async function sendEmail(strapi, params) {
488
591
  subject: params.subject
489
592
  };
490
593
  }
491
- const CONTENT_TYPE$7 = "plugin::ai-sdk.memory";
594
+ const CONTENT_TYPE$a = "plugin::ai-sdk.memory";
492
595
  const saveMemorySchema = zod.z.object({
493
596
  content: zod.z.string().describe('A short, factual statement to remember about the user (e.g. "User prefers dark mode", "Company name is Acme Corp")'),
494
597
  category: zod.z.string().optional().describe('Category for the memory (e.g. "preference", "project", "personal", "general"). Defaults to "general".')
@@ -499,7 +602,7 @@ async function saveMemory(strapi, params, context) {
499
602
  return { success: false, message: "Cannot save memory: user context not available." };
500
603
  }
501
604
  try {
502
- await strapi.documents(CONTENT_TYPE$7).create({
605
+ await strapi.documents(CONTENT_TYPE$a).create({
503
606
  data: {
504
607
  content: params.content,
505
608
  category: params.category || "general",
@@ -512,7 +615,7 @@ async function saveMemory(strapi, params, context) {
512
615
  return { success: false, message: `Failed to save memory: ${detail}` };
513
616
  }
514
617
  }
515
- const CONTENT_TYPE$6 = "plugin::ai-sdk.memory";
618
+ const CONTENT_TYPE$9 = "plugin::ai-sdk.memory";
516
619
  const recallMemoriesSchema = zod.z.object({
517
620
  query: zod.z.string().optional().describe("Optional search term to filter memories by content"),
518
621
  category: zod.z.string().optional().describe('Optional category to filter by (e.g. "preference", "project", "personal")')
@@ -530,7 +633,7 @@ async function recallMemories(strapi, params, context) {
530
633
  if (params.query) {
531
634
  filters.content = { $containsi: params.query };
532
635
  }
533
- const memories = await strapi.documents(CONTENT_TYPE$6).findMany({
636
+ const memories = await strapi.documents(CONTENT_TYPE$9).findMany({
534
637
  filters,
535
638
  fields: ["content", "category"],
536
639
  sort: { createdAt: "desc" }
@@ -556,7 +659,7 @@ const findOneContentSchema = zod.z.object({
556
659
  status: zod.z.enum(["draft", "published"]).optional().describe("Document status filter."),
557
660
  locale: zod.z.string().optional().describe('Locale code for i18n content, e.g. "en" or "fr"')
558
661
  });
559
- const findOneContentDescription = "Fetch a single document by its documentId from any Strapi content type. Returns the full document with all fields and populated relations. Use searchContent first to discover document IDs.";
662
+ const findOneContentDescription = "Fetch a single document by its exact documentId. Use this when you already have a documentId and need full details with populated relations. If you need to find documents by title, field values, or full-text search, use searchContent instead. Requires contentType and documentId.";
560
663
  async function findOneContent(strapi, params) {
561
664
  const { contentType, documentId, populate = "*", fields, status, locale } = params;
562
665
  if (!strapi.contentTypes[contentType]) {
@@ -582,7 +685,7 @@ const uploadMediaSchema = zod.z.object({
582
685
  caption: zod.z.string().optional().describe("Caption for the media file"),
583
686
  alternativeText: zod.z.string().optional().describe("Alternative text for accessibility")
584
687
  });
585
- const uploadMediaDescription = "Upload a media file from a URL to the Strapi media library. Returns the uploaded file data. To link media to a content type field, use createContent or updateContent with the file ID.";
688
+ const uploadMediaDescription = "Upload a media file from a URL to the Strapi media library. This is step 1 of a two-step process: (1) upload the file with this tool to get a file ID, then (2) link it to a content entry using createContent or updateContent with { fieldName: fileId }. Requires url. Optional: name, caption, alternativeText.";
586
689
  async function uploadMedia(strapi, params) {
587
690
  const { url, name, caption, alternativeText } = params;
588
691
  let parsedUrl;
@@ -653,7 +756,7 @@ async function uploadMedia(strapi, params) {
653
756
  usage: `To link this file to a content type field, use createContent or updateContent with: { "fieldName": ${uploadedFile.id} }`
654
757
  };
655
758
  }
656
- const CONTENT_TYPE$5 = "plugin::ai-sdk.public-memory";
759
+ const CONTENT_TYPE$8 = "plugin::ai-sdk.public-memory";
657
760
  const recallPublicMemoriesSchema = zod.z.object({
658
761
  query: zod.z.string().optional().describe("Optional search term to filter public memories by content"),
659
762
  category: zod.z.string().optional().describe('Optional category to filter by (e.g. "faq", "product", "policy")')
@@ -664,7 +767,7 @@ async function recallPublicMemories(strapi, params) {
664
767
  const filters = {};
665
768
  if (params.category) filters.category = params.category;
666
769
  if (params.query) filters.content = { $containsi: params.query };
667
- const memories = await strapi.documents(CONTENT_TYPE$5).findMany({
770
+ const memories = await strapi.documents(CONTENT_TYPE$8).findMany({
668
771
  filters,
669
772
  fields: ["content", "category"],
670
773
  sort: { createdAt: "desc" }
@@ -746,7 +849,7 @@ const aggregateContentSchema = zod.z.object({
746
849
  status: zod.z.enum(["draft", "published"]).optional().describe("Filter by document status"),
747
850
  locale: zod.z.string().optional().describe("Locale code for i18n content")
748
851
  });
749
- const aggregateContentDescription = 'Aggregate and count content entries. Use for analytics questions like "how many articles per category", "content trends by month", or total counts with filters. Prefer this over searchContent for counting and grouping.';
852
+ const aggregateContentDescription = 'Count and group content entries for analytics. Answers questions like "how many articles?", "posts per category", "content published by month". Three operations: count (total), countByField (group by a field), countByDateRange (bucket by day/week/month). Prefer this over searchContent when you need counts or distributions, not the documents themselves.';
750
853
  function buildBaseQuery(params) {
751
854
  const { filters, status, locale, dateFrom, dateTo, dateField = "createdAt" } = params;
752
855
  const query = {};
@@ -898,7 +1001,7 @@ async function aggregateContent(strapi, params) {
898
1001
  throw new Error(`Unknown operation: ${operation}`);
899
1002
  }
900
1003
  }
901
- const CONTENT_TYPE$4 = "plugin::ai-sdk.task";
1004
+ const CONTENT_TYPE$7 = "plugin::ai-sdk.task";
902
1005
  const manageTaskSchema = zod.z.object({
903
1006
  action: zod.z.enum(["create", "update", "complete", "list", "summary"]).describe(
904
1007
  "Action to perform: create a new task, update an existing task, complete (mark done), list open tasks, or get a summary."
@@ -914,14 +1017,14 @@ const manageTaskSchema = zod.z.object({
914
1017
  done: zod.z.boolean().optional().describe("Set done status explicitly (for update action)."),
915
1018
  filters: zod.z.record(zod.z.string(), zod.z.unknown()).optional().describe("Additional Strapi filters for list action.")
916
1019
  });
917
- const manageTaskDescription = "Manage the user's task list. Create, update, complete, list, or summarize tasks. Tasks are scored by consequence × impact to help prioritize what matters most.";
1020
+ const manageTaskDescription = "Manage the user's task list. Actions: create (new task), update (modify fields), complete (mark done), list (open tasks sorted by priority score), summary (overview with overdue/urgent counts). Tasks are scored by consequence × impact to prioritize what matters most. For create: title is required; consequence and impact are collected via UI form if omitted.";
918
1021
  async function resolveTask(strapi, adminUserId, documentId, title) {
919
1022
  if (documentId) {
920
- const task2 = await strapi.documents(CONTENT_TYPE$4).findOne({ documentId });
1023
+ const task2 = await strapi.documents(CONTENT_TYPE$7).findOne({ documentId });
921
1024
  if (task2 && task2.adminUserId === adminUserId) return task2;
922
1025
  }
923
1026
  if (title) {
924
- const tasks = await strapi.documents(CONTENT_TYPE$4).findMany({
1027
+ const tasks = await strapi.documents(CONTENT_TYPE$7).findMany({
925
1028
  filters: {
926
1029
  adminUserId,
927
1030
  title: { $containsi: title }
@@ -974,7 +1077,7 @@ async function manageTask(strapi, params, context) {
974
1077
  if (params.consequence !== void 0) data.consequence = params.consequence;
975
1078
  if (params.impact !== void 0) data.impact = params.impact;
976
1079
  if (params.dueDate !== void 0) data.dueDate = params.dueDate;
977
- const updated = await strapi.documents(CONTENT_TYPE$4).update({
1080
+ const updated = await strapi.documents(CONTENT_TYPE$7).update({
978
1081
  documentId: duplicate.documentId,
979
1082
  data
980
1083
  });
@@ -985,7 +1088,7 @@ async function manageTask(strapi, params, context) {
985
1088
  data: updated
986
1089
  };
987
1090
  }
988
- const task2 = await strapi.documents(CONTENT_TYPE$4).create({
1091
+ const task2 = await strapi.documents(CONTENT_TYPE$7).create({
989
1092
  data: {
990
1093
  title: params.title,
991
1094
  description: params.description,
@@ -1014,7 +1117,7 @@ async function manageTask(strapi, params, context) {
1014
1117
  for (const key of ["title", "description", "content", "priority", "consequence", "impact", "dueDate", "done"]) {
1015
1118
  if (params[key] !== void 0) data[key] = params[key];
1016
1119
  }
1017
- const updated = await strapi.documents(CONTENT_TYPE$4).update({
1120
+ const updated = await strapi.documents(CONTENT_TYPE$7).update({
1018
1121
  documentId: existing.documentId,
1019
1122
  data
1020
1123
  });
@@ -1025,7 +1128,7 @@ async function manageTask(strapi, params, context) {
1025
1128
  if (!toComplete) {
1026
1129
  return { success: false, message: "Task not found. Provide a documentId or a title to search by." };
1027
1130
  }
1028
- await strapi.documents(CONTENT_TYPE$4).update({
1131
+ await strapi.documents(CONTENT_TYPE$7).update({
1029
1132
  documentId: toComplete.documentId,
1030
1133
  data: { done: true }
1031
1134
  });
@@ -1037,7 +1140,7 @@ async function manageTask(strapi, params, context) {
1037
1140
  done: false,
1038
1141
  ...params.filters
1039
1142
  };
1040
- const tasks = await strapi.documents(CONTENT_TYPE$4).findMany({
1143
+ const tasks = await strapi.documents(CONTENT_TYPE$7).findMany({
1041
1144
  filters
1042
1145
  });
1043
1146
  const sorted = tasks.sort((a, b) => {
@@ -1066,7 +1169,7 @@ async function manageTask(strapi, params, context) {
1066
1169
  };
1067
1170
  }
1068
1171
  case "summary": {
1069
- const allOpen = await strapi.documents(CONTENT_TYPE$4).findMany({
1172
+ const allOpen = await strapi.documents(CONTENT_TYPE$7).findMany({
1070
1173
  filters: { adminUserId, done: false }
1071
1174
  });
1072
1175
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -1103,6 +1206,82 @@ async function manageTask(strapi, params, context) {
1103
1206
  return { success: false, message: `Task operation failed: ${detail}` };
1104
1207
  }
1105
1208
  }
1209
+ const CONTENT_TYPE$6 = "plugin::ai-sdk.note";
1210
+ const saveNoteSchema = zod.z.object({
1211
+ title: zod.z.string().optional().describe("A short title or label for the note"),
1212
+ content: zod.z.string().describe("The note content in markdown format. Can include code blocks, lists, links, etc."),
1213
+ category: zod.z.string().optional().describe('Category for the note: "research", "snippet", "idea", or "reference". Defaults to "research".'),
1214
+ tags: zod.z.string().optional().describe('Comma-separated tags for filtering (e.g. "strapi, api, tutorial")'),
1215
+ source: zod.z.string().optional().describe('Where this note came from (e.g. "conversation", "web research", a URL)')
1216
+ });
1217
+ const saveNoteDescription = "Save a research note, code snippet, idea, or reference for the user. Use this when the user wants to save part of a conversation, research findings, code examples, or any content they may want to use later for writing blog posts or articles. Content is saved as markdown.";
1218
+ async function saveNote(strapi, params, context) {
1219
+ if (!context?.adminUserId) {
1220
+ return { success: false, message: "Cannot save note: user context not available." };
1221
+ }
1222
+ try {
1223
+ await strapi.documents(CONTENT_TYPE$6).create({
1224
+ data: {
1225
+ title: params.title || "",
1226
+ content: params.content,
1227
+ category: params.category || "research",
1228
+ tags: params.tags || "",
1229
+ source: params.source || "conversation",
1230
+ adminUserId: context.adminUserId
1231
+ }
1232
+ });
1233
+ const label = params.title ? `"${params.title}"` : "Note";
1234
+ return { success: true, message: `${label} saved to research notes.` };
1235
+ } catch (error) {
1236
+ const detail = error instanceof Error ? error.message : String(error);
1237
+ return { success: false, message: `Failed to save note: ${detail}` };
1238
+ }
1239
+ }
1240
+ const CONTENT_TYPE$5 = "plugin::ai-sdk.note";
1241
+ const recallNotesSchema = zod.z.object({
1242
+ query: zod.z.string().optional().describe("Optional search term to filter notes by title or content"),
1243
+ category: zod.z.string().optional().describe('Optional category to filter by: "research", "snippet", "idea", "reference"'),
1244
+ tag: zod.z.string().optional().describe("Optional tag to filter by (matches within comma-separated tags)")
1245
+ });
1246
+ const recallNotesDescription = "Recall saved research notes, snippets, ideas, and references. Use this to look up previously saved notes. Without parameters, returns all notes.";
1247
+ async function recallNotes(strapi, params, context) {
1248
+ if (!context?.adminUserId) {
1249
+ return { success: false, notes: [], count: 0 };
1250
+ }
1251
+ try {
1252
+ const filters = { adminUserId: context.adminUserId };
1253
+ if (params.category) {
1254
+ filters.category = params.category;
1255
+ }
1256
+ if (params.query) {
1257
+ filters.$or = [
1258
+ { title: { $containsi: params.query } },
1259
+ { content: { $containsi: params.query } }
1260
+ ];
1261
+ }
1262
+ if (params.tag) {
1263
+ filters.tags = { $containsi: params.tag };
1264
+ }
1265
+ const notes = await strapi.documents(CONTENT_TYPE$5).findMany({
1266
+ filters,
1267
+ fields: ["title", "content", "category", "tags", "source"],
1268
+ sort: { createdAt: "desc" }
1269
+ });
1270
+ return {
1271
+ success: true,
1272
+ notes: notes.map((n) => ({
1273
+ title: n.title || "",
1274
+ content: n.content,
1275
+ category: n.category,
1276
+ tags: n.tags || "",
1277
+ source: n.source || ""
1278
+ })),
1279
+ count: notes.length
1280
+ };
1281
+ } catch (error) {
1282
+ return { success: false, notes: [], count: 0 };
1283
+ }
1284
+ }
1106
1285
  const listContentTypesTool = {
1107
1286
  name: "listContentTypes",
1108
1287
  description: listContentTypesDescription,
@@ -1233,6 +1412,20 @@ const manageTaskTool = {
1233
1412
  execute: async (args, strapi, context) => manageTask(strapi, args, context),
1234
1413
  internal: true
1235
1414
  };
1415
+ const saveNoteTool = {
1416
+ name: "saveNote",
1417
+ description: saveNoteDescription,
1418
+ schema: saveNoteSchema,
1419
+ execute: async (args, strapi, context) => saveNote(strapi, args, context),
1420
+ internal: true
1421
+ };
1422
+ const recallNotesTool = {
1423
+ name: "recallNotes",
1424
+ description: recallNotesDescription,
1425
+ schema: recallNotesSchema,
1426
+ execute: async (args, strapi, context) => recallNotes(strapi, args, context),
1427
+ internal: true
1428
+ };
1236
1429
  const builtInTools = [
1237
1430
  listContentTypesTool,
1238
1431
  searchContentTool,
@@ -1245,7 +1438,9 @@ const builtInTools = [
1245
1438
  recallMemoriesTool,
1246
1439
  recallPublicMemoriesTool,
1247
1440
  aggregateContentTool,
1248
- manageTaskTool
1441
+ manageTaskTool,
1442
+ saveNoteTool,
1443
+ recallNotesTool
1249
1444
  ];
1250
1445
  const PLUGIN_ID$2 = "ai-sdk";
1251
1446
  const bootstrap = ({ strapi }) => {
@@ -1416,17 +1611,17 @@ const config = {
1416
1611
  }
1417
1612
  }
1418
1613
  };
1419
- const kind$3 = "collectionType";
1420
- const collectionName$3 = "ai_sdk_conversations";
1421
- const info$3 = {
1614
+ const kind$4 = "collectionType";
1615
+ const collectionName$4 = "ai_sdk_conversations";
1616
+ const info$4 = {
1422
1617
  singularName: "conversation",
1423
1618
  pluralName: "conversations",
1424
1619
  displayName: "AI Conversation"
1425
1620
  };
1426
- const options$3 = {
1621
+ const options$4 = {
1427
1622
  draftAndPublish: false
1428
1623
  };
1429
- const pluginOptions$3 = {
1624
+ const pluginOptions$4 = {
1430
1625
  "content-manager": {
1431
1626
  visible: false
1432
1627
  },
@@ -1434,7 +1629,7 @@ const pluginOptions$3 = {
1434
1629
  visible: false
1435
1630
  }
1436
1631
  };
1437
- const attributes$3 = {
1632
+ const attributes$4 = {
1438
1633
  title: {
1439
1634
  type: "string",
1440
1635
  required: true,
@@ -1449,26 +1644,26 @@ const attributes$3 = {
1449
1644
  required: true
1450
1645
  }
1451
1646
  };
1452
- const schema$3 = {
1453
- kind: kind$3,
1454
- collectionName: collectionName$3,
1455
- info: info$3,
1456
- options: options$3,
1457
- pluginOptions: pluginOptions$3,
1458
- attributes: attributes$3
1647
+ const schema$4 = {
1648
+ kind: kind$4,
1649
+ collectionName: collectionName$4,
1650
+ info: info$4,
1651
+ options: options$4,
1652
+ pluginOptions: pluginOptions$4,
1653
+ attributes: attributes$4
1459
1654
  };
1460
- const conversation = { schema: schema$3 };
1461
- const kind$2 = "collectionType";
1462
- const collectionName$2 = "ai_sdk_memories";
1463
- const info$2 = {
1655
+ const conversation = { schema: schema$4 };
1656
+ const kind$3 = "collectionType";
1657
+ const collectionName$3 = "ai_sdk_memories";
1658
+ const info$3 = {
1464
1659
  singularName: "memory",
1465
1660
  pluralName: "memories",
1466
1661
  displayName: "AI Memory"
1467
1662
  };
1468
- const options$2 = {
1663
+ const options$3 = {
1469
1664
  draftAndPublish: false
1470
1665
  };
1471
- const pluginOptions$2 = {
1666
+ const pluginOptions$3 = {
1472
1667
  "content-manager": {
1473
1668
  visible: false
1474
1669
  },
@@ -1476,9 +1671,9 @@ const pluginOptions$2 = {
1476
1671
  visible: false
1477
1672
  }
1478
1673
  };
1479
- const attributes$2 = {
1674
+ const attributes$3 = {
1480
1675
  content: {
1481
- type: "text",
1676
+ type: "richtext",
1482
1677
  required: true
1483
1678
  },
1484
1679
  category: {
@@ -1496,26 +1691,26 @@ const attributes$2 = {
1496
1691
  required: true
1497
1692
  }
1498
1693
  };
1499
- const schema$2 = {
1500
- kind: kind$2,
1501
- collectionName: collectionName$2,
1502
- info: info$2,
1503
- options: options$2,
1504
- pluginOptions: pluginOptions$2,
1505
- attributes: attributes$2
1694
+ const schema$3 = {
1695
+ kind: kind$3,
1696
+ collectionName: collectionName$3,
1697
+ info: info$3,
1698
+ options: options$3,
1699
+ pluginOptions: pluginOptions$3,
1700
+ attributes: attributes$3
1506
1701
  };
1507
- const memory = { schema: schema$2 };
1508
- const kind$1 = "collectionType";
1509
- const collectionName$1 = "ai_sdk_public_memories";
1510
- const info$1 = {
1702
+ const memory = { schema: schema$3 };
1703
+ const kind$2 = "collectionType";
1704
+ const collectionName$2 = "ai_sdk_public_memories";
1705
+ const info$2 = {
1511
1706
  singularName: "public-memory",
1512
1707
  pluralName: "public-memories",
1513
1708
  displayName: "AI Public Memory"
1514
1709
  };
1515
- const options$1 = {
1710
+ const options$2 = {
1516
1711
  draftAndPublish: false
1517
1712
  };
1518
- const pluginOptions$1 = {
1713
+ const pluginOptions$2 = {
1519
1714
  "content-manager": {
1520
1715
  visible: false
1521
1716
  },
@@ -1523,7 +1718,7 @@ const pluginOptions$1 = {
1523
1718
  visible: false
1524
1719
  }
1525
1720
  };
1526
- const attributes$1 = {
1721
+ const attributes$2 = {
1527
1722
  content: {
1528
1723
  type: "text",
1529
1724
  required: true
@@ -1539,26 +1734,26 @@ const attributes$1 = {
1539
1734
  "default": "general"
1540
1735
  }
1541
1736
  };
1542
- const schema$1 = {
1543
- kind: kind$1,
1544
- collectionName: collectionName$1,
1545
- info: info$1,
1546
- options: options$1,
1547
- pluginOptions: pluginOptions$1,
1548
- attributes: attributes$1
1737
+ const schema$2 = {
1738
+ kind: kind$2,
1739
+ collectionName: collectionName$2,
1740
+ info: info$2,
1741
+ options: options$2,
1742
+ pluginOptions: pluginOptions$2,
1743
+ attributes: attributes$2
1549
1744
  };
1550
- const publicMemory = { schema: schema$1 };
1551
- const kind = "collectionType";
1552
- const collectionName = "ai_sdk_tasks";
1553
- const info = {
1745
+ const publicMemory = { schema: schema$2 };
1746
+ const kind$1 = "collectionType";
1747
+ const collectionName$1 = "ai_sdk_tasks";
1748
+ const info$1 = {
1554
1749
  singularName: "task",
1555
1750
  pluralName: "tasks",
1556
1751
  displayName: "AI Task"
1557
1752
  };
1558
- const options = {
1753
+ const options$1 = {
1559
1754
  draftAndPublish: false
1560
1755
  };
1561
- const pluginOptions = {
1756
+ const pluginOptions$1 = {
1562
1757
  "content-manager": {
1563
1758
  visible: true
1564
1759
  },
@@ -1566,7 +1761,7 @@ const pluginOptions = {
1566
1761
  visible: false
1567
1762
  }
1568
1763
  };
1569
- const attributes = {
1764
+ const attributes$1 = {
1570
1765
  title: {
1571
1766
  type: "string",
1572
1767
  required: true
@@ -1611,6 +1806,62 @@ const attributes = {
1611
1806
  required: true
1612
1807
  }
1613
1808
  };
1809
+ const schema$1 = {
1810
+ kind: kind$1,
1811
+ collectionName: collectionName$1,
1812
+ info: info$1,
1813
+ options: options$1,
1814
+ pluginOptions: pluginOptions$1,
1815
+ attributes: attributes$1
1816
+ };
1817
+ const task = { schema: schema$1 };
1818
+ const kind = "collectionType";
1819
+ const collectionName = "ai_sdk_notes";
1820
+ const info = {
1821
+ singularName: "note",
1822
+ pluralName: "notes",
1823
+ displayName: "AI Note"
1824
+ };
1825
+ const options = {
1826
+ draftAndPublish: false
1827
+ };
1828
+ const pluginOptions = {
1829
+ "content-manager": {
1830
+ visible: false
1831
+ },
1832
+ "content-type-builder": {
1833
+ visible: false
1834
+ }
1835
+ };
1836
+ const attributes = {
1837
+ title: {
1838
+ type: "string"
1839
+ },
1840
+ content: {
1841
+ type: "richtext",
1842
+ required: true
1843
+ },
1844
+ category: {
1845
+ type: "enumeration",
1846
+ "enum": [
1847
+ "research",
1848
+ "snippet",
1849
+ "idea",
1850
+ "reference"
1851
+ ],
1852
+ "default": "research"
1853
+ },
1854
+ tags: {
1855
+ type: "text"
1856
+ },
1857
+ source: {
1858
+ type: "string"
1859
+ },
1860
+ adminUserId: {
1861
+ type: "integer",
1862
+ required: true
1863
+ }
1864
+ };
1614
1865
  const schema = {
1615
1866
  kind,
1616
1867
  collectionName,
@@ -1619,8 +1870,8 @@ const schema = {
1619
1870
  pluginOptions,
1620
1871
  attributes
1621
1872
  };
1622
- const task = { schema };
1623
- const contentTypes = { conversation, memory, "public-memory": publicMemory, task };
1873
+ const note = { schema };
1874
+ const contentTypes = { conversation, memory, "public-memory": publicMemory, task, note };
1624
1875
  function getService(strapi, ctx) {
1625
1876
  const service2 = strapi.plugin("ai-sdk").service("service");
1626
1877
  if (!service2.isInitialized()) {
@@ -1936,20 +2187,20 @@ const mcpController = ({ strapi }) => {
1936
2187
  }
1937
2188
  };
1938
2189
  };
1939
- const CONTENT_TYPE$3 = "plugin::ai-sdk.conversation";
1940
- function getAdminUserId$2(ctx) {
2190
+ const CONTENT_TYPE$4 = "plugin::ai-sdk.conversation";
2191
+ function getAdminUserId$3(ctx) {
1941
2192
  const id = ctx.state?.user?.id;
1942
2193
  return typeof id === "number" ? id : null;
1943
2194
  }
1944
2195
  const conversationController = ({ strapi }) => ({
1945
2196
  async find(ctx) {
1946
- const adminUserId = getAdminUserId$2(ctx);
2197
+ const adminUserId = getAdminUserId$3(ctx);
1947
2198
  if (!adminUserId) {
1948
2199
  ctx.status = 401;
1949
2200
  ctx.body = { error: "Unauthorized" };
1950
2201
  return;
1951
2202
  }
1952
- const conversations = await strapi.documents(CONTENT_TYPE$3).findMany({
2203
+ const conversations = await strapi.documents(CONTENT_TYPE$4).findMany({
1953
2204
  filters: { adminUserId },
1954
2205
  fields: ["title", "createdAt", "updatedAt"],
1955
2206
  sort: { updatedAt: "desc" }
@@ -1957,14 +2208,14 @@ const conversationController = ({ strapi }) => ({
1957
2208
  ctx.body = { data: conversations };
1958
2209
  },
1959
2210
  async findOne(ctx) {
1960
- const adminUserId = getAdminUserId$2(ctx);
2211
+ const adminUserId = getAdminUserId$3(ctx);
1961
2212
  if (!adminUserId) {
1962
2213
  ctx.status = 401;
1963
2214
  ctx.body = { error: "Unauthorized" };
1964
2215
  return;
1965
2216
  }
1966
2217
  const { id } = ctx.params;
1967
- const conversation2 = await strapi.documents(CONTENT_TYPE$3).findOne({
2218
+ const conversation2 = await strapi.documents(CONTENT_TYPE$4).findOne({
1968
2219
  documentId: id
1969
2220
  });
1970
2221
  if (!conversation2 || conversation2.adminUserId !== adminUserId) {
@@ -1975,14 +2226,14 @@ const conversationController = ({ strapi }) => ({
1975
2226
  ctx.body = { data: conversation2 };
1976
2227
  },
1977
2228
  async create(ctx) {
1978
- const adminUserId = getAdminUserId$2(ctx);
2229
+ const adminUserId = getAdminUserId$3(ctx);
1979
2230
  if (!adminUserId) {
1980
2231
  ctx.status = 401;
1981
2232
  ctx.body = { error: "Unauthorized" };
1982
2233
  return;
1983
2234
  }
1984
2235
  const { title, messages } = ctx.request.body;
1985
- const conversation2 = await strapi.documents(CONTENT_TYPE$3).create({
2236
+ const conversation2 = await strapi.documents(CONTENT_TYPE$4).create({
1986
2237
  data: {
1987
2238
  title: title || "New conversation",
1988
2239
  messages: messages || [],
@@ -1993,14 +2244,14 @@ const conversationController = ({ strapi }) => ({
1993
2244
  ctx.body = { data: conversation2 };
1994
2245
  },
1995
2246
  async update(ctx) {
1996
- const adminUserId = getAdminUserId$2(ctx);
2247
+ const adminUserId = getAdminUserId$3(ctx);
1997
2248
  if (!adminUserId) {
1998
2249
  ctx.status = 401;
1999
2250
  ctx.body = { error: "Unauthorized" };
2000
2251
  return;
2001
2252
  }
2002
2253
  const { id } = ctx.params;
2003
- const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
2254
+ const existing = await strapi.documents(CONTENT_TYPE$4).findOne({
2004
2255
  documentId: id
2005
2256
  });
2006
2257
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -2012,21 +2263,21 @@ const conversationController = ({ strapi }) => ({
2012
2263
  const data = {};
2013
2264
  if (title !== void 0) data.title = title;
2014
2265
  if (messages !== void 0) data.messages = messages;
2015
- const conversation2 = await strapi.documents(CONTENT_TYPE$3).update({
2266
+ const conversation2 = await strapi.documents(CONTENT_TYPE$4).update({
2016
2267
  documentId: id,
2017
2268
  data
2018
2269
  });
2019
2270
  ctx.body = { data: conversation2 };
2020
2271
  },
2021
2272
  async delete(ctx) {
2022
- const adminUserId = getAdminUserId$2(ctx);
2273
+ const adminUserId = getAdminUserId$3(ctx);
2023
2274
  if (!adminUserId) {
2024
2275
  ctx.status = 401;
2025
2276
  ctx.body = { error: "Unauthorized" };
2026
2277
  return;
2027
2278
  }
2028
2279
  const { id } = ctx.params;
2029
- const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
2280
+ const existing = await strapi.documents(CONTENT_TYPE$4).findOne({
2030
2281
  documentId: id
2031
2282
  });
2032
2283
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -2034,25 +2285,25 @@ const conversationController = ({ strapi }) => ({
2034
2285
  ctx.body = { error: "Conversation not found" };
2035
2286
  return;
2036
2287
  }
2037
- await strapi.documents(CONTENT_TYPE$3).delete({ documentId: id });
2288
+ await strapi.documents(CONTENT_TYPE$4).delete({ documentId: id });
2038
2289
  ctx.status = 200;
2039
2290
  ctx.body = { data: { documentId: id } };
2040
2291
  }
2041
2292
  });
2042
- const CONTENT_TYPE$2 = "plugin::ai-sdk.memory";
2043
- function getAdminUserId$1(ctx) {
2293
+ const CONTENT_TYPE$3 = "plugin::ai-sdk.memory";
2294
+ function getAdminUserId$2(ctx) {
2044
2295
  const id = ctx.state?.user?.id;
2045
2296
  return typeof id === "number" ? id : null;
2046
2297
  }
2047
2298
  const memoryController = ({ strapi }) => ({
2048
2299
  async find(ctx) {
2049
- const adminUserId = getAdminUserId$1(ctx);
2300
+ const adminUserId = getAdminUserId$2(ctx);
2050
2301
  if (!adminUserId) {
2051
2302
  ctx.status = 401;
2052
2303
  ctx.body = { error: "Unauthorized" };
2053
2304
  return;
2054
2305
  }
2055
- const memories = await strapi.documents(CONTENT_TYPE$2).findMany({
2306
+ const memories = await strapi.documents(CONTENT_TYPE$3).findMany({
2056
2307
  filters: { adminUserId },
2057
2308
  fields: ["content", "category", "createdAt"],
2058
2309
  sort: { createdAt: "desc" }
@@ -2060,7 +2311,7 @@ const memoryController = ({ strapi }) => ({
2060
2311
  ctx.body = { data: memories };
2061
2312
  },
2062
2313
  async create(ctx) {
2063
- const adminUserId = getAdminUserId$1(ctx);
2314
+ const adminUserId = getAdminUserId$2(ctx);
2064
2315
  if (!adminUserId) {
2065
2316
  ctx.status = 401;
2066
2317
  ctx.body = { error: "Unauthorized" };
@@ -2072,7 +2323,7 @@ const memoryController = ({ strapi }) => ({
2072
2323
  ctx.body = { error: "content is required" };
2073
2324
  return;
2074
2325
  }
2075
- const memory2 = await strapi.documents(CONTENT_TYPE$2).create({
2326
+ const memory2 = await strapi.documents(CONTENT_TYPE$3).create({
2076
2327
  data: {
2077
2328
  content,
2078
2329
  category: category || "general",
@@ -2083,14 +2334,14 @@ const memoryController = ({ strapi }) => ({
2083
2334
  ctx.body = { data: memory2 };
2084
2335
  },
2085
2336
  async update(ctx) {
2086
- const adminUserId = getAdminUserId$1(ctx);
2337
+ const adminUserId = getAdminUserId$2(ctx);
2087
2338
  if (!adminUserId) {
2088
2339
  ctx.status = 401;
2089
2340
  ctx.body = { error: "Unauthorized" };
2090
2341
  return;
2091
2342
  }
2092
2343
  const { id } = ctx.params;
2093
- const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
2344
+ const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
2094
2345
  documentId: id
2095
2346
  });
2096
2347
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -2102,21 +2353,21 @@ const memoryController = ({ strapi }) => ({
2102
2353
  const data = {};
2103
2354
  if (content !== void 0) data.content = content;
2104
2355
  if (category !== void 0) data.category = category;
2105
- const memory2 = await strapi.documents(CONTENT_TYPE$2).update({
2356
+ const memory2 = await strapi.documents(CONTENT_TYPE$3).update({
2106
2357
  documentId: id,
2107
2358
  data
2108
2359
  });
2109
2360
  ctx.body = { data: memory2 };
2110
2361
  },
2111
2362
  async delete(ctx) {
2112
- const adminUserId = getAdminUserId$1(ctx);
2363
+ const adminUserId = getAdminUserId$2(ctx);
2113
2364
  if (!adminUserId) {
2114
2365
  ctx.status = 401;
2115
2366
  ctx.body = { error: "Unauthorized" };
2116
2367
  return;
2117
2368
  }
2118
2369
  const { id } = ctx.params;
2119
- const existing = await strapi.documents(CONTENT_TYPE$2).findOne({
2370
+ const existing = await strapi.documents(CONTENT_TYPE$3).findOne({
2120
2371
  documentId: id
2121
2372
  });
2122
2373
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -2124,15 +2375,15 @@ const memoryController = ({ strapi }) => ({
2124
2375
  ctx.body = { error: "Memory not found" };
2125
2376
  return;
2126
2377
  }
2127
- await strapi.documents(CONTENT_TYPE$2).delete({ documentId: id });
2378
+ await strapi.documents(CONTENT_TYPE$3).delete({ documentId: id });
2128
2379
  ctx.status = 200;
2129
2380
  ctx.body = { data: { documentId: id } };
2130
2381
  }
2131
2382
  });
2132
- const CONTENT_TYPE$1 = "plugin::ai-sdk.public-memory";
2383
+ const CONTENT_TYPE$2 = "plugin::ai-sdk.public-memory";
2133
2384
  const publicMemoryController = ({ strapi }) => ({
2134
2385
  async find(ctx) {
2135
- const memories = await strapi.documents(CONTENT_TYPE$1).findMany({
2386
+ const memories = await strapi.documents(CONTENT_TYPE$2).findMany({
2136
2387
  fields: ["content", "category", "createdAt"],
2137
2388
  sort: { createdAt: "desc" }
2138
2389
  });
@@ -2145,7 +2396,7 @@ const publicMemoryController = ({ strapi }) => ({
2145
2396
  ctx.body = { error: "content is required" };
2146
2397
  return;
2147
2398
  }
2148
- const memory2 = await strapi.documents(CONTENT_TYPE$1).create({
2399
+ const memory2 = await strapi.documents(CONTENT_TYPE$2).create({
2149
2400
  data: { content, category: category || "general" }
2150
2401
  });
2151
2402
  ctx.status = 201;
@@ -2153,7 +2404,7 @@ const publicMemoryController = ({ strapi }) => ({
2153
2404
  },
2154
2405
  async update(ctx) {
2155
2406
  const { id } = ctx.params;
2156
- const existing = await strapi.documents(CONTENT_TYPE$1).findOne({ documentId: id });
2407
+ const existing = await strapi.documents(CONTENT_TYPE$2).findOne({ documentId: id });
2157
2408
  if (!existing) {
2158
2409
  ctx.status = 404;
2159
2410
  ctx.body = { error: "Public memory not found" };
@@ -2163,7 +2414,7 @@ const publicMemoryController = ({ strapi }) => ({
2163
2414
  const data = {};
2164
2415
  if (content !== void 0) data.content = content;
2165
2416
  if (category !== void 0) data.category = category;
2166
- const memory2 = await strapi.documents(CONTENT_TYPE$1).update({
2417
+ const memory2 = await strapi.documents(CONTENT_TYPE$2).update({
2167
2418
  documentId: id,
2168
2419
  data
2169
2420
  });
@@ -2171,45 +2422,45 @@ const publicMemoryController = ({ strapi }) => ({
2171
2422
  },
2172
2423
  async delete(ctx) {
2173
2424
  const { id } = ctx.params;
2174
- const existing = await strapi.documents(CONTENT_TYPE$1).findOne({ documentId: id });
2425
+ const existing = await strapi.documents(CONTENT_TYPE$2).findOne({ documentId: id });
2175
2426
  if (!existing) {
2176
2427
  ctx.status = 404;
2177
2428
  ctx.body = { error: "Public memory not found" };
2178
2429
  return;
2179
2430
  }
2180
- await strapi.documents(CONTENT_TYPE$1).delete({ documentId: id });
2431
+ await strapi.documents(CONTENT_TYPE$2).delete({ documentId: id });
2181
2432
  ctx.status = 200;
2182
2433
  ctx.body = { data: { documentId: id } };
2183
2434
  }
2184
2435
  });
2185
- const CONTENT_TYPE = "plugin::ai-sdk.task";
2186
- function getAdminUserId(ctx) {
2436
+ const CONTENT_TYPE$1 = "plugin::ai-sdk.task";
2437
+ function getAdminUserId$1(ctx) {
2187
2438
  const id = ctx.state?.user?.id;
2188
2439
  return typeof id === "number" ? id : null;
2189
2440
  }
2190
2441
  const taskController = ({ strapi }) => ({
2191
2442
  async find(ctx) {
2192
- const adminUserId = getAdminUserId(ctx);
2443
+ const adminUserId = getAdminUserId$1(ctx);
2193
2444
  if (!adminUserId) {
2194
2445
  ctx.status = 401;
2195
2446
  ctx.body = { error: "Unauthorized" };
2196
2447
  return;
2197
2448
  }
2198
- const tasks = await strapi.documents(CONTENT_TYPE).findMany({
2449
+ const tasks = await strapi.documents(CONTENT_TYPE$1).findMany({
2199
2450
  filters: { adminUserId },
2200
2451
  sort: { createdAt: "desc" }
2201
2452
  });
2202
2453
  ctx.body = { data: tasks };
2203
2454
  },
2204
2455
  async create(ctx) {
2205
- const adminUserId = getAdminUserId(ctx);
2456
+ const adminUserId = getAdminUserId$1(ctx);
2206
2457
  if (!adminUserId) {
2207
2458
  ctx.status = 401;
2208
2459
  ctx.body = { error: "Unauthorized" };
2209
2460
  return;
2210
2461
  }
2211
2462
  const body = ctx.request.body;
2212
- const task2 = await strapi.documents(CONTENT_TYPE).create({
2463
+ const task2 = await strapi.documents(CONTENT_TYPE$1).create({
2213
2464
  data: {
2214
2465
  title: body.title,
2215
2466
  description: body.description,
@@ -2226,14 +2477,14 @@ const taskController = ({ strapi }) => ({
2226
2477
  ctx.body = { data: task2 };
2227
2478
  },
2228
2479
  async update(ctx) {
2229
- const adminUserId = getAdminUserId(ctx);
2480
+ const adminUserId = getAdminUserId$1(ctx);
2230
2481
  if (!adminUserId) {
2231
2482
  ctx.status = 401;
2232
2483
  ctx.body = { error: "Unauthorized" };
2233
2484
  return;
2234
2485
  }
2235
2486
  const { id } = ctx.params;
2236
- const existing = await strapi.documents(CONTENT_TYPE).findOne({
2487
+ const existing = await strapi.documents(CONTENT_TYPE$1).findOne({
2237
2488
  documentId: id
2238
2489
  });
2239
2490
  if (!existing || existing.adminUserId !== adminUserId) {
@@ -2246,13 +2497,80 @@ const taskController = ({ strapi }) => ({
2246
2497
  for (const key of ["title", "description", "content", "done", "priority", "consequence", "impact", "dueDate"]) {
2247
2498
  if (body[key] !== void 0) data[key] = body[key];
2248
2499
  }
2249
- const task2 = await strapi.documents(CONTENT_TYPE).update({
2500
+ const task2 = await strapi.documents(CONTENT_TYPE$1).update({
2250
2501
  documentId: id,
2251
2502
  data
2252
2503
  });
2253
2504
  ctx.body = { data: task2 };
2254
2505
  },
2255
2506
  async delete(ctx) {
2507
+ const adminUserId = getAdminUserId$1(ctx);
2508
+ if (!adminUserId) {
2509
+ ctx.status = 401;
2510
+ ctx.body = { error: "Unauthorized" };
2511
+ return;
2512
+ }
2513
+ const { id } = ctx.params;
2514
+ const existing = await strapi.documents(CONTENT_TYPE$1).findOne({
2515
+ documentId: id
2516
+ });
2517
+ if (!existing || existing.adminUserId !== adminUserId) {
2518
+ ctx.status = 404;
2519
+ ctx.body = { error: "Task not found" };
2520
+ return;
2521
+ }
2522
+ await strapi.documents(CONTENT_TYPE$1).delete({ documentId: id });
2523
+ ctx.status = 200;
2524
+ ctx.body = { data: { documentId: id } };
2525
+ }
2526
+ });
2527
+ const CONTENT_TYPE = "plugin::ai-sdk.note";
2528
+ function getAdminUserId(ctx) {
2529
+ const id = ctx.state?.user?.id;
2530
+ return typeof id === "number" ? id : null;
2531
+ }
2532
+ const noteController = ({ strapi }) => ({
2533
+ async find(ctx) {
2534
+ const adminUserId = getAdminUserId(ctx);
2535
+ if (!adminUserId) {
2536
+ ctx.status = 401;
2537
+ ctx.body = { error: "Unauthorized" };
2538
+ return;
2539
+ }
2540
+ const notes = await strapi.documents(CONTENT_TYPE).findMany({
2541
+ filters: { adminUserId },
2542
+ fields: ["title", "content", "category", "tags", "source", "createdAt"],
2543
+ sort: { createdAt: "desc" }
2544
+ });
2545
+ ctx.body = { data: notes };
2546
+ },
2547
+ async create(ctx) {
2548
+ const adminUserId = getAdminUserId(ctx);
2549
+ if (!adminUserId) {
2550
+ ctx.status = 401;
2551
+ ctx.body = { error: "Unauthorized" };
2552
+ return;
2553
+ }
2554
+ const { title, content, category, tags, source } = ctx.request.body;
2555
+ if (!content || typeof content !== "string") {
2556
+ ctx.status = 400;
2557
+ ctx.body = { error: "content is required" };
2558
+ return;
2559
+ }
2560
+ const note2 = await strapi.documents(CONTENT_TYPE).create({
2561
+ data: {
2562
+ title: title || "",
2563
+ content,
2564
+ category: category || "research",
2565
+ tags: tags || "",
2566
+ source: source || "",
2567
+ adminUserId
2568
+ }
2569
+ });
2570
+ ctx.status = 201;
2571
+ ctx.body = { data: note2 };
2572
+ },
2573
+ async update(ctx) {
2256
2574
  const adminUserId = getAdminUserId(ctx);
2257
2575
  if (!adminUserId) {
2258
2576
  ctx.status = 401;
@@ -2265,12 +2583,58 @@ const taskController = ({ strapi }) => ({
2265
2583
  });
2266
2584
  if (!existing || existing.adminUserId !== adminUserId) {
2267
2585
  ctx.status = 404;
2268
- ctx.body = { error: "Task not found" };
2586
+ ctx.body = { error: "Note not found" };
2587
+ return;
2588
+ }
2589
+ const { title, content, category, tags, source } = ctx.request.body;
2590
+ const data = {};
2591
+ if (title !== void 0) data.title = title;
2592
+ if (content !== void 0) data.content = content;
2593
+ if (category !== void 0) data.category = category;
2594
+ if (tags !== void 0) data.tags = tags;
2595
+ if (source !== void 0) data.source = source;
2596
+ const note2 = await strapi.documents(CONTENT_TYPE).update({
2597
+ documentId: id,
2598
+ data
2599
+ });
2600
+ ctx.body = { data: note2 };
2601
+ },
2602
+ async delete(ctx) {
2603
+ const adminUserId = getAdminUserId(ctx);
2604
+ if (!adminUserId) {
2605
+ ctx.status = 401;
2606
+ ctx.body = { error: "Unauthorized" };
2607
+ return;
2608
+ }
2609
+ const { id } = ctx.params;
2610
+ const existing = await strapi.documents(CONTENT_TYPE).findOne({
2611
+ documentId: id
2612
+ });
2613
+ if (!existing || existing.adminUserId !== adminUserId) {
2614
+ ctx.status = 404;
2615
+ ctx.body = { error: "Note not found" };
2269
2616
  return;
2270
2617
  }
2271
2618
  await strapi.documents(CONTENT_TYPE).delete({ documentId: id });
2272
2619
  ctx.status = 200;
2273
2620
  ctx.body = { data: { documentId: id } };
2621
+ },
2622
+ async clearAll(ctx) {
2623
+ const adminUserId = getAdminUserId(ctx);
2624
+ if (!adminUserId) {
2625
+ ctx.status = 401;
2626
+ ctx.body = { error: "Unauthorized" };
2627
+ return;
2628
+ }
2629
+ const notes = await strapi.documents(CONTENT_TYPE).findMany({
2630
+ filters: { adminUserId },
2631
+ fields: ["documentId"]
2632
+ });
2633
+ for (const note2 of notes) {
2634
+ await strapi.documents(CONTENT_TYPE).delete({ documentId: note2.documentId });
2635
+ }
2636
+ ctx.status = 200;
2637
+ ctx.body = { data: { deleted: notes.length } };
2274
2638
  }
2275
2639
  });
2276
2640
  const controllers = {
@@ -2279,7 +2643,8 @@ const controllers = {
2279
2643
  conversation: conversationController,
2280
2644
  memory: memoryController,
2281
2645
  "public-memory": publicMemoryController,
2282
- task: taskController
2646
+ task: taskController,
2647
+ note: noteController
2283
2648
  };
2284
2649
  const promptInjection = [
2285
2650
  "ignore (all |any )?(previous|prior|above) (instructions|prompts|rules)",
@@ -2649,6 +3014,36 @@ const adminAPIRoutes = {
2649
3014
  path: "/tasks/:id",
2650
3015
  handler: "task.delete",
2651
3016
  config: { policies: [] }
3017
+ },
3018
+ {
3019
+ method: "GET",
3020
+ path: "/notes",
3021
+ handler: "note.find",
3022
+ config: { policies: [] }
3023
+ },
3024
+ {
3025
+ method: "POST",
3026
+ path: "/notes",
3027
+ handler: "note.create",
3028
+ config: { policies: [] }
3029
+ },
3030
+ {
3031
+ method: "PUT",
3032
+ path: "/notes/:id",
3033
+ handler: "note.update",
3034
+ config: { policies: [] }
3035
+ },
3036
+ {
3037
+ method: "DELETE",
3038
+ path: "/notes/clear",
3039
+ handler: "note.clearAll",
3040
+ config: { policies: [] }
3041
+ },
3042
+ {
3043
+ method: "DELETE",
3044
+ path: "/notes/:id",
3045
+ handler: "note.delete",
3046
+ config: { policies: [] }
2652
3047
  }
2653
3048
  ]
2654
3049
  };
@@ -2736,6 +3131,26 @@ function describeTools(tools) {
2736
3131
  return `Available tools:
2737
3132
  ${lines.join("\n")}`;
2738
3133
  }
3134
+ function trimMessages(messages, max) {
3135
+ if (messages.length <= max) return messages;
3136
+ const sliced = messages.slice(-max);
3137
+ while (sliced.length > 0 && hasOrphanedToolCalls(sliced[0])) {
3138
+ sliced.shift();
3139
+ }
3140
+ return sliced;
3141
+ }
3142
+ function hasOrphanedToolCalls(message) {
3143
+ if (message.role !== "assistant") return false;
3144
+ if (message.parts) {
3145
+ return message.parts.some(
3146
+ (part) => part.type === "tool-invocation"
3147
+ );
3148
+ }
3149
+ if (message.toolInvocations?.length) {
3150
+ return true;
3151
+ }
3152
+ return false;
3153
+ }
2739
3154
  const DEFAULT_PREAMBLE = `You are a Strapi CMS assistant. Use your tools to fulfill user requests. When asked to create or update content, use the appropriate tool — do not tell the user you cannot. When performing bulk operations (e.g. publish multiple items), call multiple tools in parallel in a single step rather than one at a time.
2740
3155
 
2741
3156
  For analytics and counting questions (e.g. "how many", "count", "distribution", "trends", "breakdown"), use the aggregateContent tool instead of searchContent — it is faster and purpose-built for these queries. Present analytics results as markdown tables. After showing analytics results, suggest 2-3 follow-up questions the user might find useful under a "**You might also want to know:**" heading.
@@ -2799,7 +3214,7 @@ const service = ({ strapi }) => {
2799
3214
  const maxMessages = config2?.maxConversationMessages ?? DEFAULT_MAX_CONVERSATION_MESSAGES;
2800
3215
  const maxOutputTokens = config2?.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
2801
3216
  const maxSteps = config2?.maxSteps ?? DEFAULT_MAX_STEPS;
2802
- const trimmedMessages = messages.length > maxMessages ? messages.slice(-maxMessages) : messages;
3217
+ const trimmedMessages = trimMessages(messages, maxMessages);
2803
3218
  const modelMessages = await ai.convertToModelMessages(trimmedMessages);
2804
3219
  const tools = createTools(strapi, { adminUserId: options2?.adminUserId, enabledToolSources: options2?.enabledToolSources });
2805
3220
  const toolsDescription = describeTools(tools);
@@ -2844,7 +3259,7 @@ ${lines.join("\n")}`;
2844
3259
  const publicModel = publicConfig?.chatModel ?? DEFAULT_PUBLIC_CHAT_MODEL;
2845
3260
  const allowedContentTypes = publicConfig?.allowedContentTypes ?? [];
2846
3261
  const publicToolSources = publicConfig?.publicToolSources;
2847
- const trimmedMessages = messages.length > maxMessages ? messages.slice(-maxMessages) : messages;
3262
+ const trimmedMessages = trimMessages(messages, maxMessages);
2848
3263
  const modelMessages = await ai.convertToModelMessages(trimmedMessages);
2849
3264
  const tools = createPublicTools(strapi, allowedContentTypes, publicToolSources);
2850
3265
  const toolsDescription = describeTools(tools);