@karixi/payload-ai 0.1.2 → 0.1.3

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 CHANGED
@@ -87,7 +87,113 @@ mcpPlugin({
87
87
  }),
88
88
  ```
89
89
 
90
- > **Note:** `getAITools` requires the same `provider` and `apiKeyEnvVar` as `aiPlugin` so it can create the AI provider when tools are invoked. `getAIPrompts()` and `getAIResources()` take no arguments.
90
+ ### Google Gemini
91
+
92
+ ```ts
93
+ aiPlugin({
94
+ provider: 'gemini',
95
+ apiKeyEnvVar: 'GEMINI_API_KEY',
96
+ // model: 'gemini-2.5-flash', // default — or 'gemini-2.5-pro' for best quality
97
+ features: { adminUI: true },
98
+ }),
99
+
100
+ mcpPlugin({
101
+ collections: { posts: { enabled: true } },
102
+ mcp: {
103
+ tools: getAITools({
104
+ provider: 'gemini',
105
+ apiKeyEnvVar: 'GEMINI_API_KEY',
106
+ }),
107
+ prompts: getAIPrompts(),
108
+ resources: getAIResources(),
109
+ },
110
+ }),
111
+ ```
112
+
113
+ Get an API key at [ai.google.dev](https://ai.google.dev).
114
+
115
+ ### Ollama (Local / Self-Hosted)
116
+
117
+ Run AI models locally with zero API costs. Works with [Ollama](https://ollama.com), [LocalAI](https://localai.io), [vLLM](https://vllm.ai), or [LM Studio](https://lmstudio.ai) — any server with an OpenAI-compatible endpoint.
118
+
119
+ ```ts
120
+ aiPlugin({
121
+ provider: 'ollama',
122
+ apiKeyEnvVar: 'OLLAMA_API_KEY', // set to any value, e.g. 'ollama' — not validated
123
+ baseUrl: 'http://localhost:11434', // default for Ollama
124
+ model: 'llama3.3:8b', // any model you've pulled
125
+ features: { adminUI: true },
126
+ }),
127
+
128
+ mcpPlugin({
129
+ collections: { posts: { enabled: true } },
130
+ mcp: {
131
+ tools: getAITools({
132
+ provider: 'ollama',
133
+ apiKeyEnvVar: 'OLLAMA_API_KEY',
134
+ baseUrl: 'http://localhost:11434',
135
+ model: 'llama3.3:8b',
136
+ }),
137
+ prompts: getAIPrompts(),
138
+ resources: getAIResources(),
139
+ },
140
+ }),
141
+ ```
142
+
143
+ #### Quick start with Ollama
144
+
145
+ ```bash
146
+ # Install and start Ollama
147
+ curl -fsSL https://ollama.com/install.sh | sh
148
+
149
+ # Pull a model (8B is good for JSON generation)
150
+ ollama pull llama3.3:8b
151
+
152
+ # Set a dummy API key env var
153
+ export OLLAMA_API_KEY=ollama
154
+ ```
155
+
156
+ #### Docker Compose (Ollama with GPU)
157
+
158
+ ```yaml
159
+ services:
160
+ ollama:
161
+ image: ollama/ollama:latest
162
+ ports:
163
+ - "11434:11434"
164
+ volumes:
165
+ - ollama_data:/root/.ollama
166
+ deploy:
167
+ resources:
168
+ reservations:
169
+ devices:
170
+ - driver: nvidia
171
+ count: all
172
+ capabilities: [gpu]
173
+ volumes:
174
+ ollama_data:
175
+ ```
176
+
177
+ #### Other compatible servers
178
+
179
+ | Server | Default URL | Notes |
180
+ |--------|------------|-------|
181
+ | [Ollama](https://ollama.com) | `http://localhost:11434` | Easiest setup, good model library |
182
+ | [LocalAI](https://localai.io) | `http://localhost:8080` | Multi-modal (LLM + image gen + TTS) |
183
+ | [vLLM](https://vllm.ai) | `http://localhost:8000` | Highest throughput, `guided_json` for 100% schema compliance |
184
+ | [LM Studio](https://lmstudio.ai) | `http://localhost:1234` | Desktop GUI, great for macOS/Apple Silicon |
185
+
186
+ #### Recommended models for JSON generation
187
+
188
+ | Model | Ollama Tag | VRAM | Best for |
189
+ |-------|-----------|------|----------|
190
+ | Qwen3 8B | `qwen3:8b` | 8 GB | Best 8B for structured output |
191
+ | Llama 3.3 8B | `llama3.3:8b` | 8 GB | General purpose, well-tested |
192
+ | Mistral Small 3 | `mistral-small3:22b` | 16 GB | Strong mid-range |
193
+ | Qwen3 32B | `qwen3:32b` | 20 GB | Near-cloud quality |
194
+ | Llama 3.3 70B | `llama3.3:70b` | 40 GB | Best open-source quality |
195
+
196
+ > **Note:** `getAITools` requires the same `provider`, `apiKeyEnvVar`, `baseUrl`, and `model` as `aiPlugin` so it can create the AI provider when tools are invoked. `getAIPrompts()` and `getAIResources()` take no arguments.
91
197
 
92
198
  ## Connecting Claude Code
93
199
 
@@ -185,10 +291,12 @@ Adds four additional MCP tools:
185
291
  ```ts
186
292
  aiPlugin({
187
293
  // Required
188
- provider: 'anthropic' | 'openai',
294
+ provider: 'anthropic' | 'openai' | 'gemini' | 'ollama',
189
295
  apiKeyEnvVar: string, // env var name (not the key itself)
190
296
 
191
297
  // Optional
298
+ baseUrl: string, // provider endpoint (required for ollama, optional for gemini)
299
+ model: string, // model override (each provider has a sensible default)
192
300
  features: {
193
301
  adminUI: boolean, // default: false
194
302
  devTools: boolean, // default: false
@@ -0,0 +1,297 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
2
+ //#region src/core/providers/anthropic.ts
3
+ function isAnthropicResponse(value) {
4
+ return typeof value === "object" && value !== null && "content" in value && Array.isArray(value.content);
5
+ }
6
+ function createAnthropicProvider(apiKey) {
7
+ async function callAPI(messages) {
8
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
9
+ method: "POST",
10
+ headers: {
11
+ "x-api-key": apiKey,
12
+ "anthropic-version": "2023-06-01",
13
+ "content-type": "application/json"
14
+ },
15
+ body: JSON.stringify({
16
+ model: "claude-sonnet-4-20250514",
17
+ max_tokens: 8192,
18
+ messages
19
+ })
20
+ });
21
+ if (!response.ok) {
22
+ const errorText = await response.text();
23
+ throw new Error(`Anthropic API error ${response.status}: ${errorText}`);
24
+ }
25
+ const data = await response.json();
26
+ if (!isAnthropicResponse(data)) throw new Error("Unexpected Anthropic API response shape");
27
+ return data;
28
+ }
29
+ return {
30
+ async generate(prompt, _outputSchema) {
31
+ const textBlock = (await callAPI([{
32
+ role: "user",
33
+ content: `${prompt}\n\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.`
34
+ }])).content.find((block) => block.type === "text");
35
+ if (!textBlock || !("text" in textBlock) || typeof textBlock.text !== "string") throw new Error("No text content in Anthropic response");
36
+ const jsonText = textBlock.text.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
37
+ const parsed = JSON.parse(jsonText);
38
+ if (!Array.isArray(parsed)) throw new Error("Anthropic response is not a JSON array");
39
+ return parsed;
40
+ },
41
+ async analyzeImage(imageBuffer) {
42
+ const base64 = imageBuffer.toString("base64");
43
+ let mediaType = "image/jpeg";
44
+ if (imageBuffer[0] === 137 && imageBuffer[1] === 80) mediaType = "image/png";
45
+ else if (imageBuffer[0] === 71 && imageBuffer[1] === 73) mediaType = "image/gif";
46
+ else if (imageBuffer[0] === 82 && imageBuffer[1] === 73) mediaType = "image/webp";
47
+ const textBlock = (await callAPI([{
48
+ role: "user",
49
+ content: [{
50
+ type: "image",
51
+ source: {
52
+ type: "base64",
53
+ media_type: mediaType,
54
+ data: base64
55
+ }
56
+ }, {
57
+ type: "text",
58
+ text: "Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation."
59
+ }]
60
+ }])).content.find((block) => block.type === "text");
61
+ if (!textBlock || !("text" in textBlock) || typeof textBlock.text !== "string") throw new Error("No text content in Anthropic image analysis response");
62
+ return textBlock.text.trim();
63
+ }
64
+ };
65
+ }
66
+ //#endregion
67
+ //#region src/core/providers/gemini.ts
68
+ function isGeminiResponse(value) {
69
+ return typeof value === "object" && value !== null && "candidates" in value;
70
+ }
71
+ function detectMediaType$1(buffer) {
72
+ if (buffer[0] === 137 && buffer[1] === 80) return "image/png";
73
+ if (buffer[0] === 71 && buffer[1] === 73) return "image/gif";
74
+ if (buffer[0] === 82 && buffer[1] === 73) return "image/webp";
75
+ return "image/jpeg";
76
+ }
77
+ function extractText(response) {
78
+ if (!response.candidates || response.candidates.length === 0) {
79
+ const reason = response.promptFeedback?.blockReason ?? "unknown";
80
+ throw new Error(`Gemini request blocked: ${reason}`);
81
+ }
82
+ const candidate = response.candidates[0];
83
+ const text = candidate.content.parts.find((p) => "text" in p)?.text;
84
+ if (!text) throw new Error("No text content in Gemini response");
85
+ if (candidate.finishReason === "MAX_TOKENS") throw new Error("Gemini response truncated (MAX_TOKENS) — output may be incomplete");
86
+ return text.trim();
87
+ }
88
+ function createGeminiProvider(config) {
89
+ const model = config.model ?? "gemini-2.5-flash";
90
+ const baseUrl = config.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
91
+ async function callAPI(contents, systemInstruction, jsonMode) {
92
+ const body = { contents };
93
+ if (systemInstruction) body.system_instruction = { parts: [{ text: systemInstruction }] };
94
+ if (jsonMode) body.generationConfig = { responseMimeType: "application/json" };
95
+ const url = `${baseUrl}/models/${model}:generateContent`;
96
+ const response = await fetch(url, {
97
+ method: "POST",
98
+ headers: {
99
+ "x-goog-api-key": config.apiKey,
100
+ "content-type": "application/json"
101
+ },
102
+ body: JSON.stringify(body)
103
+ });
104
+ if (!response.ok) {
105
+ const errorText = await response.text();
106
+ throw new Error(`Gemini API error ${response.status}: ${errorText}`);
107
+ }
108
+ const data = await response.json();
109
+ if (!isGeminiResponse(data)) throw new Error("Unexpected Gemini API response shape");
110
+ return data;
111
+ }
112
+ return {
113
+ async generate(prompt, _outputSchema) {
114
+ const jsonText = extractText(await callAPI([{
115
+ role: "user",
116
+ parts: [{ text: `${prompt}\n\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.` }]
117
+ }], "You are a data generation assistant. Always respond with valid JSON arrays only.", true)).replace(/^```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
118
+ const parsed = JSON.parse(jsonText);
119
+ if (Array.isArray(parsed)) return parsed;
120
+ if (typeof parsed === "object" && parsed !== null && "items" in parsed) {
121
+ const items = parsed.items;
122
+ if (Array.isArray(items)) return items;
123
+ }
124
+ throw new Error("Gemini response is not a JSON array");
125
+ },
126
+ async analyzeImage(imageBuffer) {
127
+ const base64 = imageBuffer.toString("base64");
128
+ return extractText(await callAPI([{
129
+ role: "user",
130
+ parts: [{ inline_data: {
131
+ mime_type: detectMediaType$1(imageBuffer),
132
+ data: base64
133
+ } }, { text: "Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation." }]
134
+ }]));
135
+ }
136
+ };
137
+ }
138
+ //#endregion
139
+ //#region src/core/providers/ollama.ts
140
+ function isChatResponse(value) {
141
+ return typeof value === "object" && value !== null && "choices" in value && Array.isArray(value.choices);
142
+ }
143
+ function detectMediaType(buffer) {
144
+ if (buffer[0] === 137 && buffer[1] === 80) return "image/png";
145
+ if (buffer[0] === 71 && buffer[1] === 73) return "image/gif";
146
+ if (buffer[0] === 82 && buffer[1] === 73) return "image/webp";
147
+ return "image/jpeg";
148
+ }
149
+ function createOllamaProvider(config) {
150
+ const baseUrl = (config.baseUrl ?? "http://localhost:11434").replace(/\/+$/, "");
151
+ const model = config.model ?? "llama3.3:8b";
152
+ const apiKey = config.apiKey ?? "ollama";
153
+ async function callAPI(messages, jsonMode) {
154
+ const body = {
155
+ model,
156
+ messages
157
+ };
158
+ if (jsonMode) body.response_format = { type: "json_object" };
159
+ const response = await fetch(`${baseUrl}/v1/chat/completions`, {
160
+ method: "POST",
161
+ headers: {
162
+ Authorization: `Bearer ${apiKey}`,
163
+ "content-type": "application/json"
164
+ },
165
+ body: JSON.stringify(body)
166
+ });
167
+ if (!response.ok) {
168
+ const errorText = await response.text();
169
+ throw new Error(`Ollama API error ${response.status}: ${errorText}`);
170
+ }
171
+ const data = await response.json();
172
+ if (!isChatResponse(data)) throw new Error("Unexpected API response shape from local LLM server");
173
+ return data;
174
+ }
175
+ return {
176
+ async generate(prompt, _outputSchema) {
177
+ const choice = (await callAPI([{
178
+ role: "system",
179
+ content: "You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied."
180
+ }, {
181
+ role: "user",
182
+ content: `${prompt}\n\nRespond with JSON object {"items": [...]} where items is the array of generated documents.`
183
+ }], true)).choices[0];
184
+ if (!choice || choice.message.content === null) throw new Error("No content in response from local LLM server");
185
+ const parsed = JSON.parse(choice.message.content);
186
+ if (typeof parsed === "object" && parsed !== null && "items" in parsed && Array.isArray(parsed.items)) return parsed.items;
187
+ if (Array.isArray(parsed)) return parsed;
188
+ throw new Error("Local LLM response is not a JSON array");
189
+ },
190
+ async analyzeImage(imageBuffer) {
191
+ const base64 = imageBuffer.toString("base64");
192
+ const choice = (await callAPI([{
193
+ role: "user",
194
+ content: [{
195
+ type: "image_url",
196
+ image_url: { url: `data:${detectMediaType(imageBuffer)};base64,${base64}` }
197
+ }, {
198
+ type: "text",
199
+ text: "Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation."
200
+ }]
201
+ }], false)).choices[0];
202
+ if (!choice || choice.message.content === null) throw new Error("No content in image analysis response from local LLM server");
203
+ return choice.message.content.trim();
204
+ }
205
+ };
206
+ }
207
+ //#endregion
208
+ //#region src/core/providers/openai.ts
209
+ function isOpenAIResponse(value) {
210
+ return typeof value === "object" && value !== null && "choices" in value && Array.isArray(value.choices);
211
+ }
212
+ function createOpenAIProvider(apiKey) {
213
+ async function callAPI(messages, jsonMode) {
214
+ const body = {
215
+ model: "gpt-4o",
216
+ messages
217
+ };
218
+ if (jsonMode) body.response_format = { type: "json_object" };
219
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
220
+ method: "POST",
221
+ headers: {
222
+ Authorization: `Bearer ${apiKey}`,
223
+ "content-type": "application/json"
224
+ },
225
+ body: JSON.stringify(body)
226
+ });
227
+ if (!response.ok) {
228
+ const errorText = await response.text();
229
+ throw new Error(`OpenAI API error ${response.status}: ${errorText}`);
230
+ }
231
+ const data = await response.json();
232
+ if (!isOpenAIResponse(data)) throw new Error("Unexpected OpenAI API response shape");
233
+ return data;
234
+ }
235
+ return {
236
+ async generate(prompt, _outputSchema) {
237
+ const choice = (await callAPI([{
238
+ role: "system",
239
+ content: "You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied."
240
+ }, {
241
+ role: "user",
242
+ content: `${prompt}\n\nRespond with JSON object {"items": [...]} where items is the array of generated documents.`
243
+ }], true)).choices[0];
244
+ if (!choice || choice.message.content === null) throw new Error("No content in OpenAI response");
245
+ const parsed = JSON.parse(choice.message.content);
246
+ if (typeof parsed === "object" && parsed !== null && "items" in parsed && Array.isArray(parsed.items)) return parsed.items;
247
+ if (Array.isArray(parsed)) return parsed;
248
+ throw new Error("OpenAI response is not a JSON array");
249
+ },
250
+ async analyzeImage(imageBuffer) {
251
+ const base64 = imageBuffer.toString("base64");
252
+ let mediaType = "image/jpeg";
253
+ if (imageBuffer[0] === 137 && imageBuffer[1] === 80) mediaType = "image/png";
254
+ else if (imageBuffer[0] === 71 && imageBuffer[1] === 73) mediaType = "image/gif";
255
+ else if (imageBuffer[0] === 82 && imageBuffer[1] === 73) mediaType = "image/webp";
256
+ const choice = (await callAPI([{
257
+ role: "user",
258
+ content: [{
259
+ type: "image_url",
260
+ image_url: { url: `data:${mediaType};base64,${base64}` }
261
+ }, {
262
+ type: "text",
263
+ text: "Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation."
264
+ }]
265
+ }], false)).choices[0];
266
+ if (!choice || choice.message.content === null) throw new Error("No content in OpenAI image analysis response");
267
+ return choice.message.content.trim();
268
+ }
269
+ };
270
+ }
271
+ //#endregion
272
+ //#region src/core/providers/base.ts
273
+ var base_exports = /* @__PURE__ */ __exportAll({ createProvider: () => createProvider });
274
+ function createProvider(config) {
275
+ switch (config.provider) {
276
+ case "anthropic": return createAnthropicProvider(config.apiKey);
277
+ case "openai": return createOpenAIProvider(config.apiKey);
278
+ case "gemini": return createGeminiProvider({
279
+ apiKey: config.apiKey,
280
+ model: config.model,
281
+ baseUrl: config.baseUrl
282
+ });
283
+ case "ollama": return createOllamaProvider({
284
+ baseUrl: config.baseUrl,
285
+ model: config.model,
286
+ apiKey: config.apiKey
287
+ });
288
+ default: {
289
+ const _exhaustive = config.provider;
290
+ throw new Error(`Unknown provider: ${String(_exhaustive)}`);
291
+ }
292
+ }
293
+ }
294
+ //#endregion
295
+ export { createProvider as n, base_exports as t };
296
+
297
+ //# sourceMappingURL=base-B4GYFuC6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-B4GYFuC6.mjs","names":["detectMediaType"],"sources":["../src/core/providers/anthropic.ts","../src/core/providers/gemini.ts","../src/core/providers/ollama.ts","../src/core/providers/openai.ts","../src/core/providers/base.ts"],"sourcesContent":["import type { AIProvider } from '../../types.js'\n\ntype AnthropicMessage = {\n role: 'user' | 'assistant'\n content: string | AnthropicContentBlock[]\n}\n\ntype AnthropicContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }\n\ntype AnthropicResponse = {\n content: Array<{ type: string; text?: string }>\n usage?: { input_tokens: number; output_tokens: number }\n}\n\nfunction isAnthropicResponse(value: unknown): value is AnthropicResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'content' in value &&\n Array.isArray((value as AnthropicResponse).content)\n )\n}\n\nexport function createAnthropicProvider(apiKey: string): AIProvider {\n async function callAPI(messages: AnthropicMessage[]): Promise<AnthropicResponse> {\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model: 'claude-sonnet-4-20250514',\n max_tokens: 8192,\n messages,\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Anthropic API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isAnthropicResponse(data)) {\n throw new Error('Unexpected Anthropic API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: AnthropicMessage[] = [\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.`,\n },\n ]\n\n const data = await callAPI(messages)\n const textBlock = data.content.find((block) => block.type === 'text')\n if (!textBlock || !('text' in textBlock) || typeof textBlock.text !== 'string') {\n throw new Error('No text content in Anthropic response')\n }\n\n const text = textBlock.text.trim()\n // Strip markdown code fences if present\n const jsonText = text\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```\\s*$/, '')\n .trim()\n\n const parsed: unknown = JSON.parse(jsonText)\n if (!Array.isArray(parsed)) {\n throw new Error('Anthropic response is not a JSON array')\n }\n return parsed\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n // Detect image type from buffer magic bytes\n let mediaType = 'image/jpeg'\n if (imageBuffer[0] === 0x89 && imageBuffer[1] === 0x50) mediaType = 'image/png'\n else if (imageBuffer[0] === 0x47 && imageBuffer[1] === 0x49) mediaType = 'image/gif'\n else if (imageBuffer[0] === 0x52 && imageBuffer[1] === 0x49) mediaType = 'image/webp'\n\n const messages: AnthropicMessage[] = [\n {\n role: 'user',\n content: [\n {\n type: 'image',\n source: { type: 'base64', media_type: mediaType, data: base64 },\n },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages)\n const textBlock = data.content.find((block) => block.type === 'text')\n if (!textBlock || !('text' in textBlock) || typeof textBlock.text !== 'string') {\n throw new Error('No text content in Anthropic image analysis response')\n }\n return textBlock.text.trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\ntype GeminiPart = { text: string } | { inline_data: { mime_type: string; data: string } }\n\ntype GeminiContent = {\n role: 'user' | 'model'\n parts: GeminiPart[]\n}\n\ntype GeminiResponse = {\n candidates?: Array<{\n content: { parts: Array<{ text?: string }>; role: string }\n finishReason: string\n }>\n usageMetadata?: {\n promptTokenCount: number\n candidatesTokenCount: number\n totalTokenCount: number\n }\n promptFeedback?: { blockReason?: string }\n}\n\nfunction isGeminiResponse(value: unknown): value is GeminiResponse {\n return typeof value === 'object' && value !== null && 'candidates' in value\n}\n\nfunction detectMediaType(buffer: Buffer): string {\n if (buffer[0] === 0x89 && buffer[1] === 0x50) return 'image/png'\n if (buffer[0] === 0x47 && buffer[1] === 0x49) return 'image/gif'\n if (buffer[0] === 0x52 && buffer[1] === 0x49) return 'image/webp'\n return 'image/jpeg'\n}\n\nfunction extractText(response: GeminiResponse): string {\n if (!response.candidates || response.candidates.length === 0) {\n const reason = response.promptFeedback?.blockReason ?? 'unknown'\n throw new Error(`Gemini request blocked: ${reason}`)\n }\n\n const candidate = response.candidates[0]\n const text = candidate.content.parts.find((p) => 'text' in p)?.text\n if (!text) {\n throw new Error('No text content in Gemini response')\n }\n\n if (candidate.finishReason === 'MAX_TOKENS') {\n throw new Error('Gemini response truncated (MAX_TOKENS) — output may be incomplete')\n }\n\n return text.trim()\n}\n\nexport type GeminiProviderConfig = {\n apiKey: string\n model?: string\n baseUrl?: string\n}\n\nexport function createGeminiProvider(config: GeminiProviderConfig): AIProvider {\n const model = config.model ?? 'gemini-2.5-flash'\n const baseUrl = config.baseUrl ?? 'https://generativelanguage.googleapis.com/v1beta'\n\n async function callAPI(\n contents: GeminiContent[],\n systemInstruction?: string,\n jsonMode?: boolean,\n ): Promise<GeminiResponse> {\n const body: Record<string, unknown> = { contents }\n\n if (systemInstruction) {\n body.system_instruction = { parts: [{ text: systemInstruction }] }\n }\n\n if (jsonMode) {\n body.generationConfig = {\n responseMimeType: 'application/json',\n }\n }\n\n const url = `${baseUrl}/models/${model}:generateContent`\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'x-goog-api-key': config.apiKey,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Gemini API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isGeminiResponse(data)) {\n throw new Error('Unexpected Gemini API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const contents: GeminiContent[] = [\n {\n role: 'user',\n parts: [\n {\n text: `${prompt}\\n\\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.`,\n },\n ],\n },\n ]\n\n const data = await callAPI(\n contents,\n 'You are a data generation assistant. Always respond with valid JSON arrays only.',\n true,\n )\n\n const text = extractText(data)\n const jsonText = text\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```\\s*$/, '')\n .trim()\n\n const parsed: unknown = JSON.parse(jsonText)\n if (Array.isArray(parsed)) {\n return parsed\n }\n if (typeof parsed === 'object' && parsed !== null && 'items' in parsed) {\n const items = (parsed as { items: unknown }).items\n if (Array.isArray(items)) return items\n }\n throw new Error('Gemini response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n const mimeType = detectMediaType(imageBuffer)\n\n const contents: GeminiContent[] = [\n {\n role: 'user',\n parts: [\n { inline_data: { mime_type: mimeType, data: base64 } },\n {\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(contents)\n return extractText(data)\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\n/**\n * OpenAI-compatible provider for local LLM servers.\n * Works with: Ollama, LocalAI, vLLM, LM Studio, and any\n * server exposing an OpenAI-compatible /v1/chat/completions endpoint.\n */\n\ntype ChatMessage = {\n role: 'user' | 'assistant' | 'system'\n content: string | ContentBlock[]\n}\n\ntype ContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\ntype ChatResponse = {\n choices: Array<{\n message: { role: string; content: string | null }\n }>\n}\n\nfunction isChatResponse(value: unknown): value is ChatResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'choices' in value &&\n Array.isArray((value as ChatResponse).choices)\n )\n}\n\nfunction detectMediaType(buffer: Buffer): string {\n if (buffer[0] === 0x89 && buffer[1] === 0x50) return 'image/png'\n if (buffer[0] === 0x47 && buffer[1] === 0x49) return 'image/gif'\n if (buffer[0] === 0x52 && buffer[1] === 0x49) return 'image/webp'\n return 'image/jpeg'\n}\n\nexport type OllamaProviderConfig = {\n baseUrl?: string\n model?: string\n apiKey?: string\n}\n\nexport function createOllamaProvider(config: OllamaProviderConfig): AIProvider {\n const baseUrl = (config.baseUrl ?? 'http://localhost:11434').replace(/\\/+$/, '')\n const model = config.model ?? 'llama3.3:8b'\n const apiKey = config.apiKey ?? 'ollama'\n\n async function callAPI(messages: ChatMessage[], jsonMode: boolean): Promise<ChatResponse> {\n const body: Record<string, unknown> = { model, messages }\n if (jsonMode) {\n body.response_format = { type: 'json_object' }\n }\n\n const response = await fetch(`${baseUrl}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Ollama API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isChatResponse(data)) {\n throw new Error('Unexpected API response shape from local LLM server')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: ChatMessage[] = [\n {\n role: 'system',\n content:\n 'You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied.',\n },\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with JSON object {\"items\": [...]} where items is the array of generated documents.`,\n },\n ]\n\n const data = await callAPI(messages, true)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in response from local LLM server')\n }\n\n const parsed: unknown = JSON.parse(choice.message.content)\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'items' in parsed &&\n Array.isArray((parsed as { items: unknown }).items)\n ) {\n return (parsed as { items: unknown[] }).items\n }\n if (Array.isArray(parsed)) {\n return parsed\n }\n throw new Error('Local LLM response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n const mediaType = detectMediaType(imageBuffer)\n const dataUrl = `data:${mediaType};base64,${base64}`\n\n const messages: ChatMessage[] = [\n {\n role: 'user',\n content: [\n { type: 'image_url', image_url: { url: dataUrl } },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages, false)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in image analysis response from local LLM server')\n }\n return (choice.message.content as string).trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\ntype OpenAIMessage = {\n role: 'user' | 'assistant' | 'system'\n content: string | OpenAIContentBlock[]\n}\n\ntype OpenAIContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\ntype OpenAIResponse = {\n choices: Array<{\n message: { role: string; content: string | null }\n }>\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number }\n}\n\nfunction isOpenAIResponse(value: unknown): value is OpenAIResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'choices' in value &&\n Array.isArray((value as OpenAIResponse).choices)\n )\n}\n\nexport function createOpenAIProvider(apiKey: string): AIProvider {\n async function callAPI(messages: OpenAIMessage[], jsonMode: boolean): Promise<OpenAIResponse> {\n const body: Record<string, unknown> = {\n model: 'gpt-4o',\n messages,\n }\n if (jsonMode) {\n body.response_format = { type: 'json_object' }\n }\n\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`OpenAI API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isOpenAIResponse(data)) {\n throw new Error('Unexpected OpenAI API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: OpenAIMessage[] = [\n {\n role: 'system',\n content:\n 'You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied.',\n },\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with JSON object {\"items\": [...]} where items is the array of generated documents.`,\n },\n ]\n\n const data = await callAPI(messages, true)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in OpenAI response')\n }\n\n const parsed: unknown = JSON.parse(choice.message.content)\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'items' in parsed &&\n Array.isArray((parsed as { items: unknown }).items)\n ) {\n return (parsed as { items: unknown[] }).items\n }\n if (Array.isArray(parsed)) {\n return parsed\n }\n throw new Error('OpenAI response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n // Detect image type from buffer magic bytes\n let mediaType = 'image/jpeg'\n if (imageBuffer[0] === 0x89 && imageBuffer[1] === 0x50) mediaType = 'image/png'\n else if (imageBuffer[0] === 0x47 && imageBuffer[1] === 0x49) mediaType = 'image/gif'\n else if (imageBuffer[0] === 0x52 && imageBuffer[1] === 0x49) mediaType = 'image/webp'\n\n const dataUrl = `data:${mediaType};base64,${base64}`\n\n const messages: OpenAIMessage[] = [\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: { url: dataUrl },\n },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages, false)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in OpenAI image analysis response')\n }\n return choice.message.content.trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\nimport { createAnthropicProvider } from './anthropic.js'\nimport { createGeminiProvider } from './gemini.js'\nimport { createOllamaProvider } from './ollama.js'\nimport { createOpenAIProvider } from './openai.js'\n\nexport type { AIProvider }\n\nexport type ProviderConfig = {\n provider: 'anthropic' | 'openai' | 'gemini' | 'ollama'\n apiKey: string\n baseUrl?: string\n model?: string\n}\n\nexport function createProvider(config: ProviderConfig): AIProvider {\n switch (config.provider) {\n case 'anthropic':\n return createAnthropicProvider(config.apiKey)\n case 'openai':\n return createOpenAIProvider(config.apiKey)\n case 'gemini':\n return createGeminiProvider({\n apiKey: config.apiKey,\n model: config.model,\n baseUrl: config.baseUrl,\n })\n case 'ollama':\n return createOllamaProvider({\n baseUrl: config.baseUrl,\n model: config.model,\n apiKey: config.apiKey,\n })\n default: {\n const _exhaustive: never = config.provider\n throw new Error(`Unknown provider: ${String(_exhaustive)}`)\n }\n }\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -4,8 +4,10 @@ import { Config, PayloadRequest } from "payload";
4
4
  //#region src/types.d.ts
5
5
  /** Plugin configuration */
6
6
  type AIPluginConfig = {
7
- /** AI provider: 'anthropic' or 'openai' */provider: 'anthropic' | 'openai'; /** Environment variable name for the API key */
8
- apiKeyEnvVar: string; /** Feature flags */
7
+ /** AI provider */provider: 'anthropic' | 'openai' | 'gemini' | 'ollama'; /** Environment variable name for the API key */
8
+ apiKeyEnvVar: string; /** Base URL override for the AI provider (required for ollama, optional for gemini) */
9
+ baseUrl?: string; /** Model name override (defaults to a sensible model per provider) */
10
+ model?: string; /** Feature flags */
9
11
  features?: {
10
12
  /** Enable admin UI components (AI Fill buttons, bulk panel) */adminUI?: boolean; /** Enable dev tools (screenshot, visual diff, form testing) */
11
13
  devTools?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/core/prompt-builder.ts","../src/mcp/prompts.ts","../src/mcp/resources.ts","../src/mcp/tools.ts","../src/plugin.ts"],"mappings":";;;;;KACY,cAAA;6CAEV,QAAA,0BAFU;EAIV,YAAA;EAEA,QAAA;IAJA,+DAME,OAAA,YAFF;IAIE,QAAA;EAAA,GAGF;EAAA,WAAA,GAAc,MAAA,SAAe,kBAAA,GAAA;EAE7B,SAAA;IACE,iBAAA;IACA,oBAAA;IACA,qBAAA;EAAA,GAKF;EAFA,eAAA,YAE6B;EAA7B,6BAAA;AAAA;AAAA,KAGU,kBAAA;EAEK,qCAAf,MAAA,GAAS,MAAA;IAGL,OAAA;IACA,MAAA;EAAA;AAAA;;UAMW,UAAA;EACf,QAAA,CAAS,MAAA,UAAgB,YAAA,EAAc,MAAA,oBAA0B,OAAA;EACjE,YAAA,CAAa,WAAA,EAAa,MAAA,GAAS,OAAA;AAAA;;KAIzB,gBAAA;EACV,IAAA;EACA,MAAA,EAAQ,WAAA;EACR,aAAA,EAAe,gBAAA;EACf,cAAA;EACA,WAAA;EACA,eAAA;AAAA;;KAIU,WAAA;EACV,IAAA;EACA,IAAA;EACA,QAAA;EACA,IAAA;EACA,OAAA,GAAU,KAAA;IAAQ,KAAA;IAAe,KAAA;EAAA;EACjC,UAAA;EACA,OAAA;EACA,MAAA,GAAS,WAAA;EACT,MAAA,GAAS,KAAA;IAAQ,IAAA;IAAc,MAAA,EAAQ,WAAA;EAAA;EACvC,eAAA;AAAA;;KAIU,gBAAA;EACV,KAAA;EACA,UAAA;EACA,OAAA;EACA,iBAAA;AAAA;;KAIU,gBAAA;EACV,UAAA;EACA,EAAA;EACA,SAAA,EAAW,IAAA;AAAA;;KAID,aAAA;EACV,KAAA;EACA,UAAA;EACA,OAAA;EACA,MAAA;EACA,KAAA;EACA,OAAA;AAAA;;;KCxFU,iBAAA;EACV,KAAA;EACA,KAAA;EACA,MAAA;EACA,WAAA,GAAc,MAAA;AAAA;;;KCHJ,SAAA;EACV,IAAA;EACA,KAAA;EACA,WAAA;EACA,UAAA,EAAY,MAAA,SAAe,CAAA,CAAE,UAAA;EAC7B,OAAA,GACE,IAAA,EAAM,MAAA,mBACN,GAAA,EAAK,cAAA,EACL,KAAA;IACK,QAAA,EAAU,KAAA;MAAQ,OAAA;QAAW,IAAA;QAAc,IAAA;MAAA;MAAgB,IAAA;IAAA;EAAA;AAAA;AAAA,iBAGpD,YAAA,CAAA,GAAgB,SAAA;;;KCfpB,WAAA;EACV,IAAA;EACA,KAAA;EACA,WAAA;EACA,GAAA;EACA,QAAA;EACA,OAAA,MAAa,IAAA;IAAsB,QAAA,EAAU,KAAA;MAAQ,IAAA;MAAc,GAAA;IAAA;EAAA;AAAA;AAAA,iBAGrD,cAAA,CAAA,GAAkB,WAAA;;;KCDtB,OAAA;EACV,IAAA;EACA,WAAA;EACA,UAAA,EAAY,MAAA,SAAe,CAAA,CAAE,UAAA;EAC7B,OAAA,GACE,IAAA,EAAM,MAAA,mBACN,GAAA,EAAK,cAAA,EACL,KAAA,cACG,OAAA;IAAU,OAAA,EAAS,KAAA;MAAQ,IAAA;MAAc,IAAA;IAAA;EAAA;AAAA;AAAA,iBAGhC,UAAA,CAAW,YAAA,EAAc,cAAA,GAAiB,OAAA;;;;;AJlB1D;;iBKQgB,QAAA,CAAS,MAAA,EAAQ,cAAA,IACvB,cAAA,EAAgB,MAAA,KAAS,MAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/core/prompt-builder.ts","../src/mcp/prompts.ts","../src/mcp/resources.ts","../src/mcp/tools.ts","../src/plugin.ts"],"mappings":";;;;;KACY,cAAA;oBAEV,QAAA,gDAFU;EAIV,YAAA;EAEA,OAAA,WAJA;EAMA,KAAA,WAFA;EAIA,QAAA;IAAA,+DAEE,OAAA,YAEA;IAAA,QAAA;EAAA,GAG2B;EAA7B,WAAA,GAAc,MAAA,SAAe,kBAAA,GAG3B;EADF,SAAA;IACE,iBAAA;IACA,oBAAA;IACA,qBAAA;EAAA,GAK2B;EAF7B,eAAA,YAK4B;EAH5B,6BAAA;AAAA;AAAA,KAGU,kBAAA;EAED,qCAAT,MAAA,GAAS,MAAA;IAGL,OAAA;IACA,MAAA;EAAA;AAAA;;UAMW,UAAA;EACf,QAAA,CAAS,MAAA,UAAgB,YAAA,EAAc,MAAA,oBAA0B,OAAA;EACjE,YAAA,CAAa,WAAA,EAAa,MAAA,GAAS,OAAA;AAAA;;KAIzB,gBAAA;EACV,IAAA;EACA,MAAA,EAAQ,WAAA;EACR,aAAA,EAAe,gBAAA;EACf,cAAA;EACA,WAAA;EACA,eAAA;AAAA;;KAIU,WAAA;EACV,IAAA;EACA,IAAA;EACA,QAAA;EACA,IAAA;EACA,OAAA,GAAU,KAAA;IAAQ,KAAA;IAAe,KAAA;EAAA;EACjC,UAAA;EACA,OAAA;EACA,MAAA,GAAS,WAAA;EACT,MAAA,GAAS,KAAA;IAAQ,IAAA;IAAc,MAAA,EAAQ,WAAA;EAAA;EACvC,eAAA;AAAA;AAVF;AAAA,KAcY,gBAAA;EACV,KAAA;EACA,UAAA;EACA,OAAA;EACA,iBAAA;AAAA;;KAIU,gBAAA;EACV,UAAA;EACA,EAAA;EACA,SAAA,EAAW,IAAA;AAAA;;KAID,aAAA;EACV,KAAA;EACA,UAAA;EACA,OAAA;EACA,MAAA;EACA,KAAA;EACA,OAAA;AAAA;;;KC5FU,iBAAA;EACV,KAAA;EACA,KAAA;EACA,MAAA;EACA,WAAA,GAAc,MAAA;AAAA;;;KCHJ,SAAA;EACV,IAAA;EACA,KAAA;EACA,WAAA;EACA,UAAA,EAAY,MAAA,SAAe,CAAA,CAAE,UAAA;EAC7B,OAAA,GACE,IAAA,EAAM,MAAA,mBACN,GAAA,EAAK,cAAA,EACL,KAAA;IACK,QAAA,EAAU,KAAA;MAAQ,OAAA;QAAW,IAAA;QAAc,IAAA;MAAA;MAAgB,IAAA;IAAA;EAAA;AAAA;AAAA,iBAGpD,YAAA,CAAA,GAAgB,SAAA;;;KCfpB,WAAA;EACV,IAAA;EACA,KAAA;EACA,WAAA;EACA,GAAA;EACA,QAAA;EACA,OAAA,MAAa,IAAA;IAAsB,QAAA,EAAU,KAAA;MAAQ,IAAA;MAAc,GAAA;IAAA;EAAA;AAAA;AAAA,iBAGrD,cAAA,CAAA,GAAkB,WAAA;;;KCDtB,OAAA;EACV,IAAA;EACA,WAAA;EACA,UAAA,EAAY,MAAA,SAAe,CAAA,CAAE,UAAA;EAC7B,OAAA,GACE,IAAA,EAAM,MAAA,mBACN,GAAA,EAAK,cAAA,EACL,KAAA,cACG,OAAA;IAAU,OAAA,EAAS,KAAA;MAAQ,IAAA;MAAc,IAAA;IAAA;EAAA;AAAA;AAAA,iBAGhC,UAAA,CAAW,YAAA,EAAc,cAAA,GAAiB,OAAA;;;;;AJlB1D;;iBKQgB,QAAA,CAAS,MAAA,EAAQ,cAAA,IACvB,cAAA,EAAgB,MAAA,KAAS,MAAA"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as generateDocuments } from "./content-generator-fPX2DL3g.mjs";
2
- import { n as createProvider } from "./base-BPwZbeh1.mjs";
2
+ import { n as createProvider } from "./base-B4GYFuC6.mjs";
3
3
  import { n as readCollectionSchema, t as readAllCollectionSchemas } from "./schema-reader-ZkoI6Pwi.mjs";
4
4
  import { z } from "zod";
5
5
  //#region src/mcp/prompts.ts
@@ -337,7 +337,9 @@ function getAITools(pluginConfig) {
337
337
  try {
338
338
  const result = await generateDocuments(createProvider({
339
339
  provider: pluginConfig.provider,
340
- apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? ""
340
+ apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? "",
341
+ baseUrl: pluginConfig.baseUrl,
342
+ model: pluginConfig.model
341
343
  }), readCollectionSchema(req.payload, collection), {
342
344
  count,
343
345
  theme
@@ -389,7 +391,9 @@ function getAITools(pluginConfig) {
389
391
  try {
390
392
  const provider = createProvider({
391
393
  provider: pluginConfig.provider,
392
- apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? ""
394
+ apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? "",
395
+ baseUrl: pluginConfig.baseUrl,
396
+ model: pluginConfig.model
393
397
  });
394
398
  const schemas = readAllCollectionSchemas(req.payload);
395
399
  const result = await runBulkPopulation(req.payload, req, schemas, {
@@ -497,12 +501,14 @@ function createSmartDefaultsHook(pluginConfig, collectionSlug) {
497
501
  if (!collectionConfig?.fields) return data;
498
502
  const apiKey = process.env[pluginConfig.apiKeyEnvVar];
499
503
  if (!apiKey) return data;
500
- const { createProvider } = await import("./base-BPwZbeh1.mjs").then((n) => n.t);
504
+ const { createProvider } = await import("./base-B4GYFuC6.mjs").then((n) => n.t);
501
505
  const { readCollectionSchema } = await import("./schema-reader-ZkoI6Pwi.mjs").then((n) => n.r);
502
506
  const { generateDocuments } = await import("./content-generator-fPX2DL3g.mjs").then((n) => n.t);
503
507
  const provider = createProvider({
504
508
  provider: pluginConfig.provider,
505
- apiKey
509
+ apiKey,
510
+ baseUrl: pluginConfig.baseUrl,
511
+ model: pluginConfig.model
506
512
  });
507
513
  for (const [fieldName, fieldConfig] of Object.entries(collectionConfig.fields)) {
508
514
  if (!fieldConfig.enabled) continue;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/mcp/prompts.ts","../src/mcp/resources.ts","../src/generate/deletion-log.ts","../src/generate/relationship-linker.ts","../src/orchestrate/progress-tracker.ts","../src/orchestrate/bulk-runner.ts","../src/mcp/tools.ts","../src/admin/hooks/afterUpload.ts","../src/admin/hooks/beforeChange.ts","../src/plugin.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\nimport { z } from 'zod'\n\nexport type MCPPrompt = {\n name: string\n title: string\n description: string\n argsSchema: Record<string, z.ZodTypeAny>\n handler: (\n args: Record<string, unknown>,\n req: PayloadRequest,\n extra: unknown,\n ) => { messages: Array<{ content: { text: string; type: 'text' }; role: 'user' | 'assistant' }> }\n}\n\nexport function getAIPrompts(): MCPPrompt[] {\n return [\n {\n name: 'generateContentBrief',\n title: 'Generate Content Brief',\n description:\n 'Produce a structured brief that guides AI content generation for a given collection and theme.',\n argsSchema: {\n collection: z.string().describe('The collection slug to generate content for'),\n theme: z.string().describe('The overarching theme or topic for the content'),\n count: z.number().describe('Number of documents to generate'),\n },\n handler(args) {\n const collection = args.collection as string\n const theme = args.theme as string\n const count = args.count as number\n\n const text = [\n `You are preparing a content generation brief for the Payload CMS collection: **${collection}**.`,\n '',\n `**Theme**: ${theme}`,\n `**Documents to generate**: ${count}`,\n '',\n 'Please produce a structured brief that includes:',\n '1. A short description of the content style and tone that fits the theme.',\n '2. Key topics or sub-themes each document should cover.',\n '3. Any field-level guidance (titles, descriptions, categories, tags) relevant to this collection.',\n '4. Example values for the most important fields.',\n '',\n `Output the brief as a JSON object with keys: style, topics, fieldGuidance, examples.`,\n ].join('\\n')\n\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text },\n },\n ],\n }\n },\n },\n\n {\n name: 'reviewGeneratedContent',\n title: 'Review Generated Content',\n description:\n 'Return a review prompt asking an AI to evaluate and suggest improvements for a generated document.',\n argsSchema: {\n collection: z.string().describe('The collection slug the document belongs to'),\n documentId: z.string().describe('The ID of the document to review'),\n },\n handler(args) {\n const collection = args.collection as string\n const documentId = args.documentId as string\n\n const text = [\n `Please review the following generated document.`,\n '',\n `**Collection**: ${collection}`,\n `**Document ID**: ${documentId}`,\n '',\n 'Evaluate the document on:',\n '1. **Accuracy** — Is the content factually plausible and internally consistent?',\n '2. **Completeness** — Are all important fields populated with meaningful values?',\n '3. **Quality** — Is the writing style appropriate and free of obvious AI artifacts?',\n '4. **Schema compliance** — Do field values match the expected types and constraints?',\n '',\n 'Provide a structured review with a score (1–10) for each criterion and specific suggestions for improvement.',\n ].join('\\n')\n\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text },\n },\n ],\n }\n },\n },\n ]\n}\n","export type MCPResource = {\n name: string\n title: string\n description: string\n uri: string\n mimeType: string\n handler: (...args: unknown[]) => { contents: Array<{ text: string; uri: string }> }\n}\n\nexport function getAIResources(): MCPResource[] {\n return [\n {\n name: 'schemaOverview',\n title: 'Collection Schema Overview',\n description:\n 'A high-level overview of all Payload CMS collections including their slugs, field counts, and populatable status.',\n uri: 'schema://collections',\n mimeType: 'text/plain',\n handler() {\n const text = [\n 'This resource provides a schema overview of all registered Payload CMS collections.',\n '',\n 'To retrieve the full schema for a specific collection, use the `getCollectionSchema` tool.',\n 'To list all collections with field counts and populatable status, use the `listCollections` tool.',\n 'To generate documents into a single collection, use the `populateCollection` tool.',\n 'To populate multiple collections at once, use the `bulkPopulate` tool.',\n '',\n 'All schema information is derived from the live Payload configuration at runtime.',\n ].join('\\n')\n\n return {\n contents: [\n {\n uri: 'schema://collections',\n text,\n },\n ],\n }\n },\n },\n ]\n}\n","import type { Payload, PayloadRequest } from 'payload'\nimport type { DeletionLogEntry } from '../types.js'\n\n/**\n * Tracks created documents for rollback on failure.\n * Uses in-memory journal — no MongoDB transaction dependency.\n * Works on standalone MongoDB, replica sets, and Atlas.\n */\nexport class DeletionLog {\n private journal: DeletionLogEntry[] = []\n\n /** Record a created document for potential rollback */\n record(collection: string, id: string): void {\n this.journal.push({ collection, id, createdAt: new Date() })\n }\n\n /** Get all recorded entries */\n getEntries(): readonly DeletionLogEntry[] {\n return [...this.journal]\n }\n\n /** Get count of recorded entries */\n get size(): number {\n return this.journal.length\n }\n\n /**\n * Rollback all created documents by deleting them in reverse order.\n * Best-effort: if process crashes, journal is lost.\n */\n async rollback(\n payload: Payload,\n req: PayloadRequest,\n ): Promise<{\n deleted: number\n failed: Array<{ collection: string; id: string; error: string }>\n }> {\n const failed: Array<{ collection: string; id: string; error: string }> = []\n let deleted = 0\n\n // Delete in reverse order (most recent first)\n for (const entry of [...this.journal].reverse()) {\n try {\n await payload.delete({\n collection: entry.collection as Parameters<Payload['delete']>[0]['collection'],\n id: entry.id,\n req,\n })\n deleted++\n } catch (err) {\n failed.push({\n collection: entry.collection,\n id: entry.id,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n this.journal = []\n return { deleted, failed }\n }\n\n /** Clear the journal without deleting documents */\n clear(): void {\n this.journal = []\n }\n}\n","import type { Payload, PayloadRequest } from 'payload'\nimport type { CollectionSchema } from '../types.js'\n\n/**\n * Link relationship fields between generated documents.\n *\n * For each relationship defined in the schema, looks up available IDs\n * from documentIds and updates the documents in that collection.\n */\nexport async function linkRelationships(\n payload: Payload,\n req: PayloadRequest,\n schema: CollectionSchema,\n documentIds: Record<string, string[]>,\n): Promise<{ updated: number; failed: number }> {\n let updated = 0\n let failed = 0\n\n const ownIds = documentIds[schema.slug] ?? []\n if (ownIds.length === 0) return { updated, failed }\n\n for (const docId of ownIds) {\n const updateData: Record<string, unknown> = {}\n let hasUpdates = false\n\n for (const rel of schema.relationships) {\n const targetCollection = Array.isArray(rel.collection) ? rel.collection[0] : rel.collection\n\n if (!targetCollection) continue\n\n // Self-referential: assign 2–4 random sibling IDs (excluding self)\n if (rel.isSelfReferential) {\n const siblings = ownIds.filter((id) => id !== docId)\n if (siblings.length === 0) continue\n\n const count = Math.min(siblings.length, 2 + Math.floor(Math.random() * 3))\n const shuffled = [...siblings].sort(() => Math.random() - 0.5).slice(0, count)\n\n updateData[rel.field] = rel.hasMany ? shuffled : shuffled[0]\n hasUpdates = true\n continue\n }\n\n const targetIds = documentIds[targetCollection] ?? []\n if (targetIds.length === 0) continue\n\n if (rel.hasMany) {\n // Pick up to 3 random IDs\n const count = Math.min(targetIds.length, 1 + Math.floor(Math.random() * 3))\n const shuffled = [...targetIds].sort(() => Math.random() - 0.5).slice(0, count)\n updateData[rel.field] = shuffled\n } else {\n // Pick a single random ID\n const randomIndex = Math.floor(Math.random() * targetIds.length)\n updateData[rel.field] = targetIds[randomIndex]\n }\n\n hasUpdates = true\n }\n\n if (!hasUpdates) continue\n\n try {\n await payload.update({\n collection: schema.slug as Parameters<Payload['update']>[0]['collection'],\n id: docId,\n data: updateData,\n overrideAccess: true,\n req,\n })\n updated++\n } catch (err) {\n console.error(\n `[relationship-linker] Failed to update ${schema.slug}/${docId}:`,\n err instanceof Error ? err.message : String(err),\n )\n failed++\n }\n }\n\n return { updated, failed }\n}\n","import { EventEmitter } from 'node:events'\nimport type { ProgressEvent } from '../types.js'\n\n/** Create a typed EventEmitter for progress events */\nexport function createProgressEmitter(): EventEmitter {\n return new EventEmitter()\n}\n\n/** Emit a progress event on the emitter */\nexport function emitProgress(emitter: EventEmitter, event: ProgressEvent): void {\n emitter.emit('progress', event)\n}\n","import type { EventEmitter } from 'node:events'\nimport type { Payload, PayloadRequest } from 'payload'\nimport { DeletionLog } from '../generate/deletion-log.js'\nimport { linkRelationships } from '../generate/relationship-linker.js'\nimport type { AIProvider, CollectionSchema, ProgressEvent } from '../types.js'\nimport { emitProgress } from './progress-tracker.js'\n\nexport type BulkRunConfig = {\n theme: string\n counts: Record<string, number>\n provider: AIProvider\n rollbackOnError?: boolean\n mediaSource?: 'unsplash' | 'placeholder'\n}\n\nexport type BulkRunResult = {\n created: Record<string, number>\n failed: Record<string, number>\n documentIds: Record<string, string[]>\n rolledBack: boolean\n elapsed: number\n}\n\n/**\n * Run bulk population of Payload collections in dependency order.\n *\n * Flow:\n * 1. Resolve creation order from schemas\n * 2. Filter to collections requested in config.counts\n * 3. For each collection: generate documents, create them, record in deletion log\n * 4. After all: link deferred relationships\n * 5. On error + rollbackOnError: rollback all created documents\n */\nexport async function runBulkPopulation(\n payload: Payload,\n req: PayloadRequest,\n schemas: CollectionSchema[],\n config: BulkRunConfig,\n emitter?: EventEmitter,\n): Promise<BulkRunResult> {\n const startTime = Date.now()\n\n // Lazy imports to avoid circular dependency issues at module load time\n const { resolveCreationOrder } = await import('../core/dependency-resolver.js')\n const { generateDocuments } = await import('../core/content-generator.js')\n\n const created: Record<string, number> = {}\n const failed: Record<string, number> = {}\n const documentIds: Record<string, string[]> = {}\n const deletionLog = new DeletionLog()\n let rolledBack = false\n\n // Determine ordered list of slugs to process\n const orderedSlugs = resolveCreationOrder(schemas)\n const slugsToProcess = orderedSlugs.filter(\n (slug) => slug in config.counts && config.counts[slug] > 0,\n )\n\n const schemaMap = new Map(schemas.map((s) => [s.slug, s]))\n\n try {\n for (const slug of slugsToProcess) {\n const schema = schemaMap.get(slug)\n if (!schema) continue\n\n const count = config.counts[slug] ?? 0\n created[slug] = 0\n failed[slug] = 0\n documentIds[slug] = []\n\n let generatedDocs: Record<string, unknown>[]\n try {\n const result = await generateDocuments(config.provider, schema, {\n count,\n theme: config.theme,\n existingIds: documentIds,\n })\n generatedDocs = result.documents\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to generate documents for \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n failed[slug] = count\n continue\n }\n\n for (const doc of generatedDocs) {\n try {\n const record = await payload.create({\n collection: slug as Parameters<Payload['create']>[0]['collection'],\n data: doc as Record<string, unknown>,\n overrideAccess: true,\n req,\n })\n\n const id = String(record.id)\n deletionLog.record(slug, id)\n documentIds[slug].push(id)\n created[slug]++\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to create document in \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n failed[slug]++\n }\n\n if (emitter) {\n const event: ProgressEvent = {\n phase: 'create',\n collection: slug,\n created: created[slug],\n failed: failed[slug],\n total: count,\n elapsed: Date.now() - startTime,\n }\n emitProgress(emitter, event)\n }\n }\n }\n\n // Deferred: link relationships across all processed collections\n for (const slug of slugsToProcess) {\n const schema = schemaMap.get(slug)\n if (!schema || schema.relationships.length === 0) continue\n\n try {\n await linkRelationships(payload, req, schema, documentIds)\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to link relationships for \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n }\n }\n } catch (err) {\n console.error(\n '[bulk-runner] Unexpected error during bulk population:',\n err instanceof Error ? err.message : String(err),\n )\n\n if (config.rollbackOnError) {\n console.log('[bulk-runner] Rolling back created documents...')\n await deletionLog.rollback(payload, req)\n rolledBack = true\n }\n }\n\n return {\n created,\n failed,\n documentIds,\n rolledBack,\n elapsed: Date.now() - startTime,\n }\n}\n","import type { PayloadRequest } from 'payload'\nimport { z } from 'zod'\nimport { generateDocuments } from '../core/content-generator.js'\nimport { createProvider } from '../core/providers/base.js'\nimport { readAllCollectionSchemas, readCollectionSchema } from '../core/schema-reader.js'\nimport { runBulkPopulation } from '../orchestrate/bulk-runner.js'\nimport type { AIPluginConfig } from '../types.js'\n\nexport type MCPTool = {\n name: string\n description: string\n parameters: Record<string, z.ZodTypeAny>\n handler: (\n args: Record<string, unknown>,\n req: PayloadRequest,\n extra: unknown,\n ) => Promise<{ content: Array<{ text: string; type: 'text' }> }>\n}\n\nexport function getAITools(pluginConfig: AIPluginConfig): MCPTool[] {\n return [\n {\n name: 'populateCollection',\n description: 'Generate and insert AI-created documents into a Payload CMS collection.',\n parameters: {\n collection: z.string().describe('The collection slug to populate'),\n count: z.number().min(1).max(100).describe('Number of documents to generate'),\n theme: z\n .string()\n .optional()\n .describe('Optional theme or topic to guide content generation'),\n },\n async handler(args, req) {\n const collection = args.collection as string\n const count = args.count as number\n const theme = (args.theme as string | undefined) ?? 'general'\n\n try {\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? '',\n })\n\n const schema = readCollectionSchema(req.payload, collection)\n const result = await generateDocuments(provider, schema, { count, theme })\n\n let created = 0\n let failed = 0\n\n for (const doc of result.documents) {\n try {\n await req.payload.create({\n collection: collection as Parameters<typeof req.payload.create>[0]['collection'],\n data: doc as Record<string, unknown>,\n overrideAccess: true,\n req,\n })\n created++\n } catch {\n failed++\n }\n }\n\n const text = `Populated \"${collection}\": ${created} created, ${failed} failed (requested ${count}).`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error populating \"${collection}\": ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'bulkPopulate',\n description:\n 'Generate and insert AI-created documents across multiple Payload CMS collections at once.',\n parameters: {\n theme: z\n .string()\n .describe('Theme or topic to guide content generation across all collections'),\n counts: z\n .string()\n .describe('JSON map of collection slug to count, e.g. {\"posts\":5,\"categories\":3}'),\n },\n async handler(args, req) {\n const theme = args.theme as string\n const countsRaw = args.counts as string\n\n let counts: Record<string, number>\n try {\n counts = JSON.parse(countsRaw) as Record<string, number>\n } catch {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: \"counts\" must be valid JSON, e.g. {\"posts\":5,\"categories\":3}',\n },\n ],\n }\n }\n\n try {\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? '',\n })\n\n const schemas = readAllCollectionSchemas(req.payload)\n const result = await runBulkPopulation(req.payload, req, schemas, {\n theme,\n counts,\n provider,\n rollbackOnError: pluginConfig.rollbackOnError,\n })\n\n const summary = Object.entries(result.created)\n .map(([slug, n]) => `${slug}: ${n} created, ${result.failed[slug] ?? 0} failed`)\n .join('\\n')\n\n const text = `Bulk population complete in ${result.elapsed}ms${result.rolledBack ? ' (rolled back)' : ''}:\\n${summary}`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error during bulk population: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'getCollectionSchema',\n description: 'Return the analyzed schema for a single Payload CMS collection as JSON.',\n parameters: {\n collection: z.string().describe('The collection slug to inspect'),\n },\n async handler(args, req) {\n const collection = args.collection as string\n\n try {\n const schema = readCollectionSchema(req.payload, collection)\n const text = JSON.stringify(schema, null, 2)\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error reading schema for \"${collection}\": ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'listCollections',\n description:\n 'List all Payload CMS collections with their field counts and whether they can be auto-populated.',\n parameters: {},\n async handler(_args, req) {\n try {\n const schemas = readAllCollectionSchemas(req.payload)\n const lines = schemas.map(\n (s) => `- ${s.slug}: ${s.fields.length} fields, populatable=${s.populatable}`,\n )\n const text = `Collections (${schemas.length}):\\n${lines.join('\\n')}`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error listing collections: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n ]\n}\n","import type { CollectionAfterChangeHook } from 'payload'\nimport type { AIPluginConfig } from '../../types.js'\n\n/**\n * Creates an afterChange hook for the media collection that auto-generates alt text.\n * Only runs on 'create' operations where alt text is empty.\n */\nexport function createAltTextHook(pluginConfig: AIPluginConfig): CollectionAfterChangeHook {\n return async ({ doc, operation, req }) => {\n if (operation !== 'create') return doc\n\n // Only process if alt is empty\n if (doc.alt && String(doc.alt).trim() !== '') return doc\n\n const apiKey = process.env[pluginConfig.apiKeyEnvVar]\n if (!apiKey) return doc\n\n // Check if there's an uploaded file URL to analyze\n const imageUrl = doc.url || doc.filename\n if (!imageUrl) return doc\n\n try {\n // TODO: Use AI vision API (provider.analyzeImage) to analyze actual image content.\n // For now, generate descriptive alt text from the filename.\n const filename = doc.filename || 'image'\n const cleanName = String(filename)\n .replace(/[-_]/g, ' ')\n .replace(/\\.[^.]+$/, '')\n\n const altText = `${cleanName} - uploaded media`\n\n // Update the document with generated alt text\n await req.payload.update({\n collection: 'media',\n id: doc.id as string,\n data: { alt: altText },\n req,\n })\n\n return { ...doc, alt: altText }\n } catch (err) {\n console.warn('[@karixi/payload-ai] Alt text generation failed:', err)\n return doc\n }\n }\n}\n","import type { CollectionBeforeChangeHook } from 'payload'\nimport type { AIPluginConfig } from '../../types.js'\n\n/**\n * Creates a beforeChange hook that auto-fills empty fields with AI content.\n * Only runs on 'create' operations. Only fills fields configured in AIPluginConfig.\n */\nexport function createSmartDefaultsHook(\n pluginConfig: AIPluginConfig,\n collectionSlug: string,\n): CollectionBeforeChangeHook {\n return async ({ data, operation, req }) => {\n if (operation !== 'create') return data\n\n const collectionConfig = pluginConfig.collections?.[collectionSlug]\n if (!collectionConfig?.fields) return data\n\n const apiKey = process.env[pluginConfig.apiKeyEnvVar]\n if (!apiKey) return data\n\n // Import dynamically to avoid circular deps\n const { createProvider } = await import('../../core/providers/base.js')\n const { readCollectionSchema } = await import('../../core/schema-reader.js')\n const { generateDocuments } = await import('../../core/content-generator.js')\n\n const provider = createProvider({ provider: pluginConfig.provider, apiKey })\n\n // For each configured field that is empty in the data, generate a value\n for (const [fieldName, fieldConfig] of Object.entries(collectionConfig.fields)) {\n if (!fieldConfig.enabled) continue\n const currentValue = (data as Record<string, unknown>)[fieldName]\n if (currentValue !== undefined && currentValue !== null && currentValue !== '') continue\n\n try {\n const schema = readCollectionSchema(req.payload, collectionSlug)\n const fieldSchema = schema.fields.find((f) => f.name === fieldName)\n if (!fieldSchema) continue\n\n const result = await generateDocuments(\n provider,\n {\n ...schema,\n fields: [fieldSchema],\n requiredFields: [fieldName],\n },\n { count: 1, theme: fieldConfig.prompt },\n )\n\n const generated = result.documents[0]\n if (generated?.[fieldName] !== undefined) {\n ;(data as Record<string, unknown>)[fieldName] = generated[fieldName]\n }\n } catch (err) {\n console.warn(`[@karixi/payload-ai] Smart default failed for ${fieldName}:`, err)\n }\n }\n\n return data\n }\n}\n","import type { CollectionConfig, Config, Payload } from 'payload'\nimport { createAltTextHook } from './admin/hooks/afterUpload.js'\nimport { createSmartDefaultsHook } from './admin/hooks/beforeChange.js'\nimport type { AIPluginConfig } from './types.js'\n\n/**\n * Payload AI Plugin — adds AI-powered data generation and admin features.\n * Auto-injects MCP custom tools if @payloadcms/plugin-mcp is present.\n */\nexport function aiPlugin(config: AIPluginConfig) {\n return (incomingConfig: Config): Config => {\n // Validate API key env var exists at init time\n const apiKey = process.env[config.apiKeyEnvVar]\n if (!apiKey) {\n console.warn(\n `[@karixi/payload-ai] Warning: ${config.apiKeyEnvVar} environment variable is not set. AI features will not work.`,\n )\n }\n\n const existingOnInit = incomingConfig.onInit\n\n // TODO Phase 2: Inject schema introspection + AI generation\n // TODO Phase 3: Register MCP custom tools (auto-detect mcpPlugin)\n\n let collections = incomingConfig.collections\n\n if (config.features?.adminUI && collections) {\n collections = collections.map((collection: CollectionConfig) => {\n const slug = collection.slug\n const updatedHooks = { ...collection.hooks }\n\n // Add beforeChange smart defaults hook for configured collections\n if (config.collections?.[slug]) {\n updatedHooks.beforeChange = [\n ...(updatedHooks.beforeChange ?? []),\n createSmartDefaultsHook(config, slug),\n ]\n }\n\n // Add afterChange alt text hook for media collection\n if (slug === 'media') {\n updatedHooks.afterChange = [\n ...(updatedHooks.afterChange ?? []),\n createAltTextHook(config),\n ]\n }\n\n if (\n updatedHooks.beforeChange !== collection.hooks?.beforeChange ||\n updatedHooks.afterChange !== collection.hooks?.afterChange\n ) {\n return { ...collection, hooks: updatedHooks }\n }\n\n return collection\n })\n }\n\n return {\n ...incomingConfig,\n collections,\n onInit: async (payload: Payload) => {\n if (existingOnInit) await existingOnInit(payload)\n console.log(`[@karixi/payload-ai] Plugin initialized. Provider: ${config.provider}`)\n // TODO Phase 4: Initialize admin UI components\n // TODO Phase 5: Initialize dev tools\n },\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/mcp/prompts.ts","../src/mcp/resources.ts","../src/generate/deletion-log.ts","../src/generate/relationship-linker.ts","../src/orchestrate/progress-tracker.ts","../src/orchestrate/bulk-runner.ts","../src/mcp/tools.ts","../src/admin/hooks/afterUpload.ts","../src/admin/hooks/beforeChange.ts","../src/plugin.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\nimport { z } from 'zod'\n\nexport type MCPPrompt = {\n name: string\n title: string\n description: string\n argsSchema: Record<string, z.ZodTypeAny>\n handler: (\n args: Record<string, unknown>,\n req: PayloadRequest,\n extra: unknown,\n ) => { messages: Array<{ content: { text: string; type: 'text' }; role: 'user' | 'assistant' }> }\n}\n\nexport function getAIPrompts(): MCPPrompt[] {\n return [\n {\n name: 'generateContentBrief',\n title: 'Generate Content Brief',\n description:\n 'Produce a structured brief that guides AI content generation for a given collection and theme.',\n argsSchema: {\n collection: z.string().describe('The collection slug to generate content for'),\n theme: z.string().describe('The overarching theme or topic for the content'),\n count: z.number().describe('Number of documents to generate'),\n },\n handler(args) {\n const collection = args.collection as string\n const theme = args.theme as string\n const count = args.count as number\n\n const text = [\n `You are preparing a content generation brief for the Payload CMS collection: **${collection}**.`,\n '',\n `**Theme**: ${theme}`,\n `**Documents to generate**: ${count}`,\n '',\n 'Please produce a structured brief that includes:',\n '1. A short description of the content style and tone that fits the theme.',\n '2. Key topics or sub-themes each document should cover.',\n '3. Any field-level guidance (titles, descriptions, categories, tags) relevant to this collection.',\n '4. Example values for the most important fields.',\n '',\n `Output the brief as a JSON object with keys: style, topics, fieldGuidance, examples.`,\n ].join('\\n')\n\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text },\n },\n ],\n }\n },\n },\n\n {\n name: 'reviewGeneratedContent',\n title: 'Review Generated Content',\n description:\n 'Return a review prompt asking an AI to evaluate and suggest improvements for a generated document.',\n argsSchema: {\n collection: z.string().describe('The collection slug the document belongs to'),\n documentId: z.string().describe('The ID of the document to review'),\n },\n handler(args) {\n const collection = args.collection as string\n const documentId = args.documentId as string\n\n const text = [\n `Please review the following generated document.`,\n '',\n `**Collection**: ${collection}`,\n `**Document ID**: ${documentId}`,\n '',\n 'Evaluate the document on:',\n '1. **Accuracy** — Is the content factually plausible and internally consistent?',\n '2. **Completeness** — Are all important fields populated with meaningful values?',\n '3. **Quality** — Is the writing style appropriate and free of obvious AI artifacts?',\n '4. **Schema compliance** — Do field values match the expected types and constraints?',\n '',\n 'Provide a structured review with a score (1–10) for each criterion and specific suggestions for improvement.',\n ].join('\\n')\n\n return {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text },\n },\n ],\n }\n },\n },\n ]\n}\n","export type MCPResource = {\n name: string\n title: string\n description: string\n uri: string\n mimeType: string\n handler: (...args: unknown[]) => { contents: Array<{ text: string; uri: string }> }\n}\n\nexport function getAIResources(): MCPResource[] {\n return [\n {\n name: 'schemaOverview',\n title: 'Collection Schema Overview',\n description:\n 'A high-level overview of all Payload CMS collections including their slugs, field counts, and populatable status.',\n uri: 'schema://collections',\n mimeType: 'text/plain',\n handler() {\n const text = [\n 'This resource provides a schema overview of all registered Payload CMS collections.',\n '',\n 'To retrieve the full schema for a specific collection, use the `getCollectionSchema` tool.',\n 'To list all collections with field counts and populatable status, use the `listCollections` tool.',\n 'To generate documents into a single collection, use the `populateCollection` tool.',\n 'To populate multiple collections at once, use the `bulkPopulate` tool.',\n '',\n 'All schema information is derived from the live Payload configuration at runtime.',\n ].join('\\n')\n\n return {\n contents: [\n {\n uri: 'schema://collections',\n text,\n },\n ],\n }\n },\n },\n ]\n}\n","import type { Payload, PayloadRequest } from 'payload'\nimport type { DeletionLogEntry } from '../types.js'\n\n/**\n * Tracks created documents for rollback on failure.\n * Uses in-memory journal — no MongoDB transaction dependency.\n * Works on standalone MongoDB, replica sets, and Atlas.\n */\nexport class DeletionLog {\n private journal: DeletionLogEntry[] = []\n\n /** Record a created document for potential rollback */\n record(collection: string, id: string): void {\n this.journal.push({ collection, id, createdAt: new Date() })\n }\n\n /** Get all recorded entries */\n getEntries(): readonly DeletionLogEntry[] {\n return [...this.journal]\n }\n\n /** Get count of recorded entries */\n get size(): number {\n return this.journal.length\n }\n\n /**\n * Rollback all created documents by deleting them in reverse order.\n * Best-effort: if process crashes, journal is lost.\n */\n async rollback(\n payload: Payload,\n req: PayloadRequest,\n ): Promise<{\n deleted: number\n failed: Array<{ collection: string; id: string; error: string }>\n }> {\n const failed: Array<{ collection: string; id: string; error: string }> = []\n let deleted = 0\n\n // Delete in reverse order (most recent first)\n for (const entry of [...this.journal].reverse()) {\n try {\n await payload.delete({\n collection: entry.collection as Parameters<Payload['delete']>[0]['collection'],\n id: entry.id,\n req,\n })\n deleted++\n } catch (err) {\n failed.push({\n collection: entry.collection,\n id: entry.id,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n this.journal = []\n return { deleted, failed }\n }\n\n /** Clear the journal without deleting documents */\n clear(): void {\n this.journal = []\n }\n}\n","import type { Payload, PayloadRequest } from 'payload'\nimport type { CollectionSchema } from '../types.js'\n\n/**\n * Link relationship fields between generated documents.\n *\n * For each relationship defined in the schema, looks up available IDs\n * from documentIds and updates the documents in that collection.\n */\nexport async function linkRelationships(\n payload: Payload,\n req: PayloadRequest,\n schema: CollectionSchema,\n documentIds: Record<string, string[]>,\n): Promise<{ updated: number; failed: number }> {\n let updated = 0\n let failed = 0\n\n const ownIds = documentIds[schema.slug] ?? []\n if (ownIds.length === 0) return { updated, failed }\n\n for (const docId of ownIds) {\n const updateData: Record<string, unknown> = {}\n let hasUpdates = false\n\n for (const rel of schema.relationships) {\n const targetCollection = Array.isArray(rel.collection) ? rel.collection[0] : rel.collection\n\n if (!targetCollection) continue\n\n // Self-referential: assign 2–4 random sibling IDs (excluding self)\n if (rel.isSelfReferential) {\n const siblings = ownIds.filter((id) => id !== docId)\n if (siblings.length === 0) continue\n\n const count = Math.min(siblings.length, 2 + Math.floor(Math.random() * 3))\n const shuffled = [...siblings].sort(() => Math.random() - 0.5).slice(0, count)\n\n updateData[rel.field] = rel.hasMany ? shuffled : shuffled[0]\n hasUpdates = true\n continue\n }\n\n const targetIds = documentIds[targetCollection] ?? []\n if (targetIds.length === 0) continue\n\n if (rel.hasMany) {\n // Pick up to 3 random IDs\n const count = Math.min(targetIds.length, 1 + Math.floor(Math.random() * 3))\n const shuffled = [...targetIds].sort(() => Math.random() - 0.5).slice(0, count)\n updateData[rel.field] = shuffled\n } else {\n // Pick a single random ID\n const randomIndex = Math.floor(Math.random() * targetIds.length)\n updateData[rel.field] = targetIds[randomIndex]\n }\n\n hasUpdates = true\n }\n\n if (!hasUpdates) continue\n\n try {\n await payload.update({\n collection: schema.slug as Parameters<Payload['update']>[0]['collection'],\n id: docId,\n data: updateData,\n overrideAccess: true,\n req,\n })\n updated++\n } catch (err) {\n console.error(\n `[relationship-linker] Failed to update ${schema.slug}/${docId}:`,\n err instanceof Error ? err.message : String(err),\n )\n failed++\n }\n }\n\n return { updated, failed }\n}\n","import { EventEmitter } from 'node:events'\nimport type { ProgressEvent } from '../types.js'\n\n/** Create a typed EventEmitter for progress events */\nexport function createProgressEmitter(): EventEmitter {\n return new EventEmitter()\n}\n\n/** Emit a progress event on the emitter */\nexport function emitProgress(emitter: EventEmitter, event: ProgressEvent): void {\n emitter.emit('progress', event)\n}\n","import type { EventEmitter } from 'node:events'\nimport type { Payload, PayloadRequest } from 'payload'\nimport { DeletionLog } from '../generate/deletion-log.js'\nimport { linkRelationships } from '../generate/relationship-linker.js'\nimport type { AIProvider, CollectionSchema, ProgressEvent } from '../types.js'\nimport { emitProgress } from './progress-tracker.js'\n\nexport type BulkRunConfig = {\n theme: string\n counts: Record<string, number>\n provider: AIProvider\n rollbackOnError?: boolean\n mediaSource?: 'unsplash' | 'placeholder'\n}\n\nexport type BulkRunResult = {\n created: Record<string, number>\n failed: Record<string, number>\n documentIds: Record<string, string[]>\n rolledBack: boolean\n elapsed: number\n}\n\n/**\n * Run bulk population of Payload collections in dependency order.\n *\n * Flow:\n * 1. Resolve creation order from schemas\n * 2. Filter to collections requested in config.counts\n * 3. For each collection: generate documents, create them, record in deletion log\n * 4. After all: link deferred relationships\n * 5. On error + rollbackOnError: rollback all created documents\n */\nexport async function runBulkPopulation(\n payload: Payload,\n req: PayloadRequest,\n schemas: CollectionSchema[],\n config: BulkRunConfig,\n emitter?: EventEmitter,\n): Promise<BulkRunResult> {\n const startTime = Date.now()\n\n // Lazy imports to avoid circular dependency issues at module load time\n const { resolveCreationOrder } = await import('../core/dependency-resolver.js')\n const { generateDocuments } = await import('../core/content-generator.js')\n\n const created: Record<string, number> = {}\n const failed: Record<string, number> = {}\n const documentIds: Record<string, string[]> = {}\n const deletionLog = new DeletionLog()\n let rolledBack = false\n\n // Determine ordered list of slugs to process\n const orderedSlugs = resolveCreationOrder(schemas)\n const slugsToProcess = orderedSlugs.filter(\n (slug) => slug in config.counts && config.counts[slug] > 0,\n )\n\n const schemaMap = new Map(schemas.map((s) => [s.slug, s]))\n\n try {\n for (const slug of slugsToProcess) {\n const schema = schemaMap.get(slug)\n if (!schema) continue\n\n const count = config.counts[slug] ?? 0\n created[slug] = 0\n failed[slug] = 0\n documentIds[slug] = []\n\n let generatedDocs: Record<string, unknown>[]\n try {\n const result = await generateDocuments(config.provider, schema, {\n count,\n theme: config.theme,\n existingIds: documentIds,\n })\n generatedDocs = result.documents\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to generate documents for \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n failed[slug] = count\n continue\n }\n\n for (const doc of generatedDocs) {\n try {\n const record = await payload.create({\n collection: slug as Parameters<Payload['create']>[0]['collection'],\n data: doc as Record<string, unknown>,\n overrideAccess: true,\n req,\n })\n\n const id = String(record.id)\n deletionLog.record(slug, id)\n documentIds[slug].push(id)\n created[slug]++\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to create document in \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n failed[slug]++\n }\n\n if (emitter) {\n const event: ProgressEvent = {\n phase: 'create',\n collection: slug,\n created: created[slug],\n failed: failed[slug],\n total: count,\n elapsed: Date.now() - startTime,\n }\n emitProgress(emitter, event)\n }\n }\n }\n\n // Deferred: link relationships across all processed collections\n for (const slug of slugsToProcess) {\n const schema = schemaMap.get(slug)\n if (!schema || schema.relationships.length === 0) continue\n\n try {\n await linkRelationships(payload, req, schema, documentIds)\n } catch (err) {\n console.error(\n `[bulk-runner] Failed to link relationships for \"${slug}\":`,\n err instanceof Error ? err.message : String(err),\n )\n }\n }\n } catch (err) {\n console.error(\n '[bulk-runner] Unexpected error during bulk population:',\n err instanceof Error ? err.message : String(err),\n )\n\n if (config.rollbackOnError) {\n console.log('[bulk-runner] Rolling back created documents...')\n await deletionLog.rollback(payload, req)\n rolledBack = true\n }\n }\n\n return {\n created,\n failed,\n documentIds,\n rolledBack,\n elapsed: Date.now() - startTime,\n }\n}\n","import type { PayloadRequest } from 'payload'\nimport { z } from 'zod'\nimport { generateDocuments } from '../core/content-generator.js'\nimport { createProvider } from '../core/providers/base.js'\nimport { readAllCollectionSchemas, readCollectionSchema } from '../core/schema-reader.js'\nimport { runBulkPopulation } from '../orchestrate/bulk-runner.js'\nimport type { AIPluginConfig } from '../types.js'\n\nexport type MCPTool = {\n name: string\n description: string\n parameters: Record<string, z.ZodTypeAny>\n handler: (\n args: Record<string, unknown>,\n req: PayloadRequest,\n extra: unknown,\n ) => Promise<{ content: Array<{ text: string; type: 'text' }> }>\n}\n\nexport function getAITools(pluginConfig: AIPluginConfig): MCPTool[] {\n return [\n {\n name: 'populateCollection',\n description: 'Generate and insert AI-created documents into a Payload CMS collection.',\n parameters: {\n collection: z.string().describe('The collection slug to populate'),\n count: z.number().min(1).max(100).describe('Number of documents to generate'),\n theme: z\n .string()\n .optional()\n .describe('Optional theme or topic to guide content generation'),\n },\n async handler(args, req) {\n const collection = args.collection as string\n const count = args.count as number\n const theme = (args.theme as string | undefined) ?? 'general'\n\n try {\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? '',\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n\n const schema = readCollectionSchema(req.payload, collection)\n const result = await generateDocuments(provider, schema, { count, theme })\n\n let created = 0\n let failed = 0\n\n for (const doc of result.documents) {\n try {\n await req.payload.create({\n collection: collection as Parameters<typeof req.payload.create>[0]['collection'],\n data: doc as Record<string, unknown>,\n overrideAccess: true,\n req,\n })\n created++\n } catch {\n failed++\n }\n }\n\n const text = `Populated \"${collection}\": ${created} created, ${failed} failed (requested ${count}).`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error populating \"${collection}\": ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'bulkPopulate',\n description:\n 'Generate and insert AI-created documents across multiple Payload CMS collections at once.',\n parameters: {\n theme: z\n .string()\n .describe('Theme or topic to guide content generation across all collections'),\n counts: z\n .string()\n .describe('JSON map of collection slug to count, e.g. {\"posts\":5,\"categories\":3}'),\n },\n async handler(args, req) {\n const theme = args.theme as string\n const countsRaw = args.counts as string\n\n let counts: Record<string, number>\n try {\n counts = JSON.parse(countsRaw) as Record<string, number>\n } catch {\n return {\n content: [\n {\n type: 'text',\n text: 'Error: \"counts\" must be valid JSON, e.g. {\"posts\":5,\"categories\":3}',\n },\n ],\n }\n }\n\n try {\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey: process.env[pluginConfig.apiKeyEnvVar] ?? '',\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n\n const schemas = readAllCollectionSchemas(req.payload)\n const result = await runBulkPopulation(req.payload, req, schemas, {\n theme,\n counts,\n provider,\n rollbackOnError: pluginConfig.rollbackOnError,\n })\n\n const summary = Object.entries(result.created)\n .map(([slug, n]) => `${slug}: ${n} created, ${result.failed[slug] ?? 0} failed`)\n .join('\\n')\n\n const text = `Bulk population complete in ${result.elapsed}ms${result.rolledBack ? ' (rolled back)' : ''}:\\n${summary}`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error during bulk population: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'getCollectionSchema',\n description: 'Return the analyzed schema for a single Payload CMS collection as JSON.',\n parameters: {\n collection: z.string().describe('The collection slug to inspect'),\n },\n async handler(args, req) {\n const collection = args.collection as string\n\n try {\n const schema = readCollectionSchema(req.payload, collection)\n const text = JSON.stringify(schema, null, 2)\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error reading schema for \"${collection}\": ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n\n {\n name: 'listCollections',\n description:\n 'List all Payload CMS collections with their field counts and whether they can be auto-populated.',\n parameters: {},\n async handler(_args, req) {\n try {\n const schemas = readAllCollectionSchemas(req.payload)\n const lines = schemas.map(\n (s) => `- ${s.slug}: ${s.fields.length} fields, populatable=${s.populatable}`,\n )\n const text = `Collections (${schemas.length}):\\n${lines.join('\\n')}`\n return { content: [{ type: 'text', text }] }\n } catch (err) {\n const text = `Error listing collections: ${err instanceof Error ? err.message : String(err)}`\n return { content: [{ type: 'text', text }] }\n }\n },\n },\n ]\n}\n","import type { CollectionAfterChangeHook } from 'payload'\nimport type { AIPluginConfig } from '../../types.js'\n\n/**\n * Creates an afterChange hook for the media collection that auto-generates alt text.\n * Only runs on 'create' operations where alt text is empty.\n */\nexport function createAltTextHook(pluginConfig: AIPluginConfig): CollectionAfterChangeHook {\n return async ({ doc, operation, req }) => {\n if (operation !== 'create') return doc\n\n // Only process if alt is empty\n if (doc.alt && String(doc.alt).trim() !== '') return doc\n\n const apiKey = process.env[pluginConfig.apiKeyEnvVar]\n if (!apiKey) return doc\n\n // Check if there's an uploaded file URL to analyze\n const imageUrl = doc.url || doc.filename\n if (!imageUrl) return doc\n\n try {\n // TODO: Use AI vision API (provider.analyzeImage) to analyze actual image content.\n // For now, generate descriptive alt text from the filename.\n const filename = doc.filename || 'image'\n const cleanName = String(filename)\n .replace(/[-_]/g, ' ')\n .replace(/\\.[^.]+$/, '')\n\n const altText = `${cleanName} - uploaded media`\n\n // Update the document with generated alt text\n await req.payload.update({\n collection: 'media',\n id: doc.id as string,\n data: { alt: altText },\n req,\n })\n\n return { ...doc, alt: altText }\n } catch (err) {\n console.warn('[@karixi/payload-ai] Alt text generation failed:', err)\n return doc\n }\n }\n}\n","import type { CollectionBeforeChangeHook } from 'payload'\nimport type { AIPluginConfig } from '../../types.js'\n\n/**\n * Creates a beforeChange hook that auto-fills empty fields with AI content.\n * Only runs on 'create' operations. Only fills fields configured in AIPluginConfig.\n */\nexport function createSmartDefaultsHook(\n pluginConfig: AIPluginConfig,\n collectionSlug: string,\n): CollectionBeforeChangeHook {\n return async ({ data, operation, req }) => {\n if (operation !== 'create') return data\n\n const collectionConfig = pluginConfig.collections?.[collectionSlug]\n if (!collectionConfig?.fields) return data\n\n const apiKey = process.env[pluginConfig.apiKeyEnvVar]\n if (!apiKey) return data\n\n // Import dynamically to avoid circular deps\n const { createProvider } = await import('../../core/providers/base.js')\n const { readCollectionSchema } = await import('../../core/schema-reader.js')\n const { generateDocuments } = await import('../../core/content-generator.js')\n\n const provider = createProvider({\n provider: pluginConfig.provider,\n apiKey,\n baseUrl: pluginConfig.baseUrl,\n model: pluginConfig.model,\n })\n\n // For each configured field that is empty in the data, generate a value\n for (const [fieldName, fieldConfig] of Object.entries(collectionConfig.fields)) {\n if (!fieldConfig.enabled) continue\n const currentValue = (data as Record<string, unknown>)[fieldName]\n if (currentValue !== undefined && currentValue !== null && currentValue !== '') continue\n\n try {\n const schema = readCollectionSchema(req.payload, collectionSlug)\n const fieldSchema = schema.fields.find((f) => f.name === fieldName)\n if (!fieldSchema) continue\n\n const result = await generateDocuments(\n provider,\n {\n ...schema,\n fields: [fieldSchema],\n requiredFields: [fieldName],\n },\n { count: 1, theme: fieldConfig.prompt },\n )\n\n const generated = result.documents[0]\n if (generated?.[fieldName] !== undefined) {\n ;(data as Record<string, unknown>)[fieldName] = generated[fieldName]\n }\n } catch (err) {\n console.warn(`[@karixi/payload-ai] Smart default failed for ${fieldName}:`, err)\n }\n }\n\n return data\n }\n}\n","import type { CollectionConfig, Config, Payload } from 'payload'\nimport { createAltTextHook } from './admin/hooks/afterUpload.js'\nimport { createSmartDefaultsHook } from './admin/hooks/beforeChange.js'\nimport type { AIPluginConfig } from './types.js'\n\n/**\n * Payload AI Plugin — adds AI-powered data generation and admin features.\n * Auto-injects MCP custom tools if @payloadcms/plugin-mcp is present.\n */\nexport function aiPlugin(config: AIPluginConfig) {\n return (incomingConfig: Config): Config => {\n // Validate API key env var exists at init time\n const apiKey = process.env[config.apiKeyEnvVar]\n if (!apiKey) {\n console.warn(\n `[@karixi/payload-ai] Warning: ${config.apiKeyEnvVar} environment variable is not set. AI features will not work.`,\n )\n }\n\n const existingOnInit = incomingConfig.onInit\n\n // TODO Phase 2: Inject schema introspection + AI generation\n // TODO Phase 3: Register MCP custom tools (auto-detect mcpPlugin)\n\n let collections = incomingConfig.collections\n\n if (config.features?.adminUI && collections) {\n collections = collections.map((collection: CollectionConfig) => {\n const slug = collection.slug\n const updatedHooks = { ...collection.hooks }\n\n // Add beforeChange smart defaults hook for configured collections\n if (config.collections?.[slug]) {\n updatedHooks.beforeChange = [\n ...(updatedHooks.beforeChange ?? []),\n createSmartDefaultsHook(config, slug),\n ]\n }\n\n // Add afterChange alt text hook for media collection\n if (slug === 'media') {\n updatedHooks.afterChange = [\n ...(updatedHooks.afterChange ?? []),\n createAltTextHook(config),\n ]\n }\n\n if (\n updatedHooks.beforeChange !== collection.hooks?.beforeChange ||\n updatedHooks.afterChange !== collection.hooks?.afterChange\n ) {\n return { ...collection, hooks: updatedHooks }\n }\n\n return collection\n })\n }\n\n return {\n ...incomingConfig,\n collections,\n onInit: async (payload: Payload) => {\n if (existingOnInit) await existingOnInit(payload)\n console.log(`[@karixi/payload-ai] Plugin initialized. Provider: ${config.provider}`)\n // TODO Phase 4: Initialize admin UI components\n // TODO Phase 5: Initialize dev tools\n },\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karixi/payload-ai",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "types": "dist/index.d.mts",
@@ -1,146 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
2
- //#region src/core/providers/anthropic.ts
3
- function isAnthropicResponse(value) {
4
- return typeof value === "object" && value !== null && "content" in value && Array.isArray(value.content);
5
- }
6
- function createAnthropicProvider(apiKey) {
7
- async function callAPI(messages) {
8
- const response = await fetch("https://api.anthropic.com/v1/messages", {
9
- method: "POST",
10
- headers: {
11
- "x-api-key": apiKey,
12
- "anthropic-version": "2023-06-01",
13
- "content-type": "application/json"
14
- },
15
- body: JSON.stringify({
16
- model: "claude-sonnet-4-20250514",
17
- max_tokens: 8192,
18
- messages
19
- })
20
- });
21
- if (!response.ok) {
22
- const errorText = await response.text();
23
- throw new Error(`Anthropic API error ${response.status}: ${errorText}`);
24
- }
25
- const data = await response.json();
26
- if (!isAnthropicResponse(data)) throw new Error("Unexpected Anthropic API response shape");
27
- return data;
28
- }
29
- return {
30
- async generate(prompt, _outputSchema) {
31
- const textBlock = (await callAPI([{
32
- role: "user",
33
- content: `${prompt}\n\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.`
34
- }])).content.find((block) => block.type === "text");
35
- if (!textBlock || !("text" in textBlock) || typeof textBlock.text !== "string") throw new Error("No text content in Anthropic response");
36
- const jsonText = textBlock.text.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```\s*$/, "").trim();
37
- const parsed = JSON.parse(jsonText);
38
- if (!Array.isArray(parsed)) throw new Error("Anthropic response is not a JSON array");
39
- return parsed;
40
- },
41
- async analyzeImage(imageBuffer) {
42
- const base64 = imageBuffer.toString("base64");
43
- let mediaType = "image/jpeg";
44
- if (imageBuffer[0] === 137 && imageBuffer[1] === 80) mediaType = "image/png";
45
- else if (imageBuffer[0] === 71 && imageBuffer[1] === 73) mediaType = "image/gif";
46
- else if (imageBuffer[0] === 82 && imageBuffer[1] === 73) mediaType = "image/webp";
47
- const textBlock = (await callAPI([{
48
- role: "user",
49
- content: [{
50
- type: "image",
51
- source: {
52
- type: "base64",
53
- media_type: mediaType,
54
- data: base64
55
- }
56
- }, {
57
- type: "text",
58
- text: "Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation."
59
- }]
60
- }])).content.find((block) => block.type === "text");
61
- if (!textBlock || !("text" in textBlock) || typeof textBlock.text !== "string") throw new Error("No text content in Anthropic image analysis response");
62
- return textBlock.text.trim();
63
- }
64
- };
65
- }
66
- //#endregion
67
- //#region src/core/providers/openai.ts
68
- function isOpenAIResponse(value) {
69
- return typeof value === "object" && value !== null && "choices" in value && Array.isArray(value.choices);
70
- }
71
- function createOpenAIProvider(apiKey) {
72
- async function callAPI(messages, jsonMode) {
73
- const body = {
74
- model: "gpt-4o",
75
- messages
76
- };
77
- if (jsonMode) body.response_format = { type: "json_object" };
78
- const response = await fetch("https://api.openai.com/v1/chat/completions", {
79
- method: "POST",
80
- headers: {
81
- Authorization: `Bearer ${apiKey}`,
82
- "content-type": "application/json"
83
- },
84
- body: JSON.stringify(body)
85
- });
86
- if (!response.ok) {
87
- const errorText = await response.text();
88
- throw new Error(`OpenAI API error ${response.status}: ${errorText}`);
89
- }
90
- const data = await response.json();
91
- if (!isOpenAIResponse(data)) throw new Error("Unexpected OpenAI API response shape");
92
- return data;
93
- }
94
- return {
95
- async generate(prompt, _outputSchema) {
96
- const choice = (await callAPI([{
97
- role: "system",
98
- content: "You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied."
99
- }, {
100
- role: "user",
101
- content: `${prompt}\n\nRespond with JSON object {"items": [...]} where items is the array of generated documents.`
102
- }], true)).choices[0];
103
- if (!choice || choice.message.content === null) throw new Error("No content in OpenAI response");
104
- const parsed = JSON.parse(choice.message.content);
105
- if (typeof parsed === "object" && parsed !== null && "items" in parsed && Array.isArray(parsed.items)) return parsed.items;
106
- if (Array.isArray(parsed)) return parsed;
107
- throw new Error("OpenAI response is not a JSON array");
108
- },
109
- async analyzeImage(imageBuffer) {
110
- const base64 = imageBuffer.toString("base64");
111
- let mediaType = "image/jpeg";
112
- if (imageBuffer[0] === 137 && imageBuffer[1] === 80) mediaType = "image/png";
113
- else if (imageBuffer[0] === 71 && imageBuffer[1] === 73) mediaType = "image/gif";
114
- else if (imageBuffer[0] === 82 && imageBuffer[1] === 73) mediaType = "image/webp";
115
- const choice = (await callAPI([{
116
- role: "user",
117
- content: [{
118
- type: "image_url",
119
- image_url: { url: `data:${mediaType};base64,${base64}` }
120
- }, {
121
- type: "text",
122
- text: "Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation."
123
- }]
124
- }], false)).choices[0];
125
- if (!choice || choice.message.content === null) throw new Error("No content in OpenAI image analysis response");
126
- return choice.message.content.trim();
127
- }
128
- };
129
- }
130
- //#endregion
131
- //#region src/core/providers/base.ts
132
- var base_exports = /* @__PURE__ */ __exportAll({ createProvider: () => createProvider });
133
- function createProvider(config) {
134
- switch (config.provider) {
135
- case "anthropic": return createAnthropicProvider(config.apiKey);
136
- case "openai": return createOpenAIProvider(config.apiKey);
137
- default: {
138
- const _exhaustive = config.provider;
139
- throw new Error(`Unknown provider: ${String(_exhaustive)}`);
140
- }
141
- }
142
- }
143
- //#endregion
144
- export { createProvider as n, base_exports as t };
145
-
146
- //# sourceMappingURL=base-BPwZbeh1.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"base-BPwZbeh1.mjs","names":[],"sources":["../src/core/providers/anthropic.ts","../src/core/providers/openai.ts","../src/core/providers/base.ts"],"sourcesContent":["import type { AIProvider } from '../../types.js'\n\ntype AnthropicMessage = {\n role: 'user' | 'assistant'\n content: string | AnthropicContentBlock[]\n}\n\ntype AnthropicContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }\n\ntype AnthropicResponse = {\n content: Array<{ type: string; text?: string }>\n usage?: { input_tokens: number; output_tokens: number }\n}\n\nfunction isAnthropicResponse(value: unknown): value is AnthropicResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'content' in value &&\n Array.isArray((value as AnthropicResponse).content)\n )\n}\n\nexport function createAnthropicProvider(apiKey: string): AIProvider {\n async function callAPI(messages: AnthropicMessage[]): Promise<AnthropicResponse> {\n const response = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model: 'claude-sonnet-4-20250514',\n max_tokens: 8192,\n messages,\n }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`Anthropic API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isAnthropicResponse(data)) {\n throw new Error('Unexpected Anthropic API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: AnthropicMessage[] = [\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with ONLY a valid JSON array. No markdown, no explanation, just the JSON array.`,\n },\n ]\n\n const data = await callAPI(messages)\n const textBlock = data.content.find((block) => block.type === 'text')\n if (!textBlock || !('text' in textBlock) || typeof textBlock.text !== 'string') {\n throw new Error('No text content in Anthropic response')\n }\n\n const text = textBlock.text.trim()\n // Strip markdown code fences if present\n const jsonText = text\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```\\s*$/, '')\n .trim()\n\n const parsed: unknown = JSON.parse(jsonText)\n if (!Array.isArray(parsed)) {\n throw new Error('Anthropic response is not a JSON array')\n }\n return parsed\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n // Detect image type from buffer magic bytes\n let mediaType = 'image/jpeg'\n if (imageBuffer[0] === 0x89 && imageBuffer[1] === 0x50) mediaType = 'image/png'\n else if (imageBuffer[0] === 0x47 && imageBuffer[1] === 0x49) mediaType = 'image/gif'\n else if (imageBuffer[0] === 0x52 && imageBuffer[1] === 0x49) mediaType = 'image/webp'\n\n const messages: AnthropicMessage[] = [\n {\n role: 'user',\n content: [\n {\n type: 'image',\n source: { type: 'base64', media_type: mediaType, data: base64 },\n },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages)\n const textBlock = data.content.find((block) => block.type === 'text')\n if (!textBlock || !('text' in textBlock) || typeof textBlock.text !== 'string') {\n throw new Error('No text content in Anthropic image analysis response')\n }\n return textBlock.text.trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\n\ntype OpenAIMessage = {\n role: 'user' | 'assistant' | 'system'\n content: string | OpenAIContentBlock[]\n}\n\ntype OpenAIContentBlock =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\ntype OpenAIResponse = {\n choices: Array<{\n message: { role: string; content: string | null }\n }>\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number }\n}\n\nfunction isOpenAIResponse(value: unknown): value is OpenAIResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'choices' in value &&\n Array.isArray((value as OpenAIResponse).choices)\n )\n}\n\nexport function createOpenAIProvider(apiKey: string): AIProvider {\n async function callAPI(messages: OpenAIMessage[], jsonMode: boolean): Promise<OpenAIResponse> {\n const body: Record<string, unknown> = {\n model: 'gpt-4o',\n messages,\n }\n if (jsonMode) {\n body.response_format = { type: 'json_object' }\n }\n\n const response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(body),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`OpenAI API error ${response.status}: ${errorText}`)\n }\n\n const data: unknown = await response.json()\n if (!isOpenAIResponse(data)) {\n throw new Error('Unexpected OpenAI API response shape')\n }\n return data\n }\n\n return {\n async generate(prompt: string, _outputSchema: Record<string, unknown>): Promise<unknown[]> {\n const messages: OpenAIMessage[] = [\n {\n role: 'system',\n content:\n 'You are a data generation assistant. Always respond with valid JSON only. When asked for an array, wrap it in {\"items\": [...]} so json_object mode is satisfied.',\n },\n {\n role: 'user',\n content: `${prompt}\\n\\nRespond with JSON object {\"items\": [...]} where items is the array of generated documents.`,\n },\n ]\n\n const data = await callAPI(messages, true)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in OpenAI response')\n }\n\n const parsed: unknown = JSON.parse(choice.message.content)\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'items' in parsed &&\n Array.isArray((parsed as { items: unknown }).items)\n ) {\n return (parsed as { items: unknown[] }).items\n }\n if (Array.isArray(parsed)) {\n return parsed\n }\n throw new Error('OpenAI response is not a JSON array')\n },\n\n async analyzeImage(imageBuffer: Buffer): Promise<string> {\n const base64 = imageBuffer.toString('base64')\n // Detect image type from buffer magic bytes\n let mediaType = 'image/jpeg'\n if (imageBuffer[0] === 0x89 && imageBuffer[1] === 0x50) mediaType = 'image/png'\n else if (imageBuffer[0] === 0x47 && imageBuffer[1] === 0x49) mediaType = 'image/gif'\n else if (imageBuffer[0] === 0x52 && imageBuffer[1] === 0x49) mediaType = 'image/webp'\n\n const dataUrl = `data:${mediaType};base64,${base64}`\n\n const messages: OpenAIMessage[] = [\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: { url: dataUrl },\n },\n {\n type: 'text',\n text: 'Describe this image concisely for use as alt text. Focus on the main subject and important visual details. Respond with only the alt text description, no extra explanation.',\n },\n ],\n },\n ]\n\n const data = await callAPI(messages, false)\n const choice = data.choices[0]\n if (!choice || choice.message.content === null) {\n throw new Error('No content in OpenAI image analysis response')\n }\n return choice.message.content.trim()\n },\n }\n}\n","import type { AIProvider } from '../../types.js'\nimport { createAnthropicProvider } from './anthropic.js'\nimport { createOpenAIProvider } from './openai.js'\n\nexport type { AIProvider }\n\nexport type ProviderConfig = {\n provider: 'anthropic' | 'openai'\n apiKey: string\n}\n\nexport function createProvider(config: ProviderConfig): AIProvider {\n switch (config.provider) {\n case 'anthropic':\n return createAnthropicProvider(config.apiKey)\n case 'openai':\n return createOpenAIProvider(config.apiKey)\n default: {\n const _exhaustive: never = config.provider\n throw new Error(`Unknown provider: ${String(_exhaustive)}`)\n }\n }\n}\n"],"mappings":"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"}