Files
at-mintel/packages/payload-ai/src/endpoints/chatEndpoint.ts
Marc Mintel 4eb1aaf640
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m14s
Monorepo Pipeline / 🧹 Lint (push) Failing after 2m4s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m39s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
ci: fix strict TS overloaded parameter matching inside useChat and payload tool bindings
2026-03-06 16:10:50 +01:00

143 lines
4.4 KiB
TypeScript

import { streamText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { generatePayloadLocalTools } from "../tools/payloadLocal.js";
import { createMcpTools } from "../tools/mcpAdapter.js";
import { generateMemoryTools } from "../tools/memoryDb.js";
import type { PayloadRequest } from "payload";
const openrouter = createOpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY || "dummy_key",
});
export const handleMcpChat = async (req: PayloadRequest) => {
if (!req.user) {
return Response.json(
{ error: "Unauthorized. You must be logged in to use AI Chat." },
{ status: 401 },
);
}
const { messages, pageContext } = ((await req.json?.()) || {
messages: [],
}) as { messages: any[]; pageContext?: any };
// 1. Check AI Permissions for req.user
// Look up the collection for permissions
const permissionsQuery = await req.payload.find({
collection: "ai-chat-permissions" as any,
where: {
or: [
{ targetUser: { equals: req.user.id } },
{ targetRole: { equals: req.user.role || "admin" } },
],
},
limit: 10,
});
const allowedCollections = new Set<string>();
const allowedMcpServers = new Set<string>();
for (const perm of permissionsQuery.docs) {
if (perm.allowedCollections) {
perm.allowedCollections.forEach((c: string) => allowedCollections.add(c));
}
if (perm.allowedMcpServers) {
perm.allowedMcpServers.forEach((s: string) => allowedMcpServers.add(s));
}
}
let accessCollections = Array.from(allowedCollections);
if (accessCollections.length === 0) {
// Fallback or demo config if not configured yet
accessCollections = [
"users",
"pages",
"posts",
"products",
"leads",
"media",
];
}
let activeTools: Record<string, any> = {};
// 2. Generate Payload Local Tools
if (accessCollections.length > 0) {
const payloadTools = generatePayloadLocalTools(
req.payload,
req,
accessCollections,
);
activeTools = { ...activeTools, ...payloadTools };
}
// 3. Connect External MCPs
if (Array.from(allowedMcpServers).includes("gitea")) {
try {
const { tools: giteaTools } = await createMcpTools({
name: "gitea",
command: "npx",
args: [
"-y",
"@modelcontextprotocol/server-gitea",
"--url",
"https://git.mintel.int",
"--token",
process.env.GITEA_TOKEN || "",
],
});
activeTools = { ...activeTools, ...giteaTools };
} catch (e) {
console.error("Failed to connect to Gitea MCP", e);
}
}
// 4. Inject Memory Database Tools
// We provide the user ID so memory is partitioned per user
const memoryTools = generateMemoryTools(req.user.id);
activeTools = { ...activeTools, ...memoryTools };
// 5. Build prompt to ensure it asks before saving
const memorySystemPrompt = `
You have access to a long-term vector memory database (Qdrant).
If the user says "speicher das", "merk dir das", "vergiss das nicht" etc., you MUST use the save_memory tool.
If the user shares important context but doesn't explicitly ask you to remember it, you should ask "Soll ich mir das für die Zukunft merken?" before saving it. Do not ask for trivial things.
`;
const contextContextStr = pageContext
? `
Current User Context:
URL: ${pageContext.url || "Unknown"}
Title: ${pageContext.title || "Unknown"}
Collection: ${pageContext.collectionSlug || "None"}
Document ID: ${pageContext.id || "None"}
You can use this to understand what the user is currently looking at.
`
: "";
try {
const result = streamText({
model: openrouter("google/gemini-3.0-flash"),
messages,
tools: activeTools,
// @ts-expect-error - AI SDK type mismatch with maxSteps
maxSteps: 10,
system: `You are a helpful Payload CMS Agent orchestrating the local Mintel ecosystem.
You only have access to tools explicitly granted by the Admin.
You can completely control Payload CMS (read, create, update, delete documents).
If you need more details to fulfill a request (e.g. creating a blog post), you can ask the user.
${contextContextStr}
${memorySystemPrompt}`,
});
return result.toTextStreamResponse();
} catch (error) {
console.error("AI Error:", error);
return Response.json(
{ error: "Failed to process AI request" },
{ status: 500 },
);
}
};