ci: fix unhandled typescript exceptions and strict eslint errors caught by the pipeline
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧹 Lint (push) Failing after 9s
Monorepo Pipeline / 🏗️ Build (push) Failing after 9s
Monorepo Pipeline / 🧪 Test (push) Failing after 9s
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

This commit is contained in:
2026-03-06 15:49:45 +01:00
parent 2dc61e4937
commit 61f2f83e0c
10 changed files with 1914 additions and 1223 deletions

View File

@@ -1,115 +1,143 @@
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'
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',
})
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 })
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));
}
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))
}
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 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);
}
}
let activeTools: Record<string, any> = {}
// 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 };
// 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 = `
// 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 ? `
const contextContextStr = pageContext
? `
Current User Context:
URL: ${pageContext.url || 'Unknown'}
Title: ${pageContext.title || 'Unknown'}
Collection: ${pageContext.collectionSlug || 'None'}
Document ID: ${pageContext.id || 'None'}
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({
// @ts-ignore - AI SDK type mismatch
model: openrouter('google/gemini-3.0-flash'),
messages,
tools: activeTools,
// @ts-ignore - AI SDK type mismatch with maxSteps
maxSteps: 10,
system: `You are a helpful Payload CMS Agent orchestrating the local Mintel ecosystem.
try {
const result = streamText({
// @ts-expect-error - AI SDK type mismatch
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}`
})
${memorySystemPrompt}`,
});
return result.toTextStreamResponse()
} catch (error) {
console.error("AI Error:", error)
return Response.json({ error: 'Failed to process AI request' }, { status: 500 })
}
}
return result.toTextStreamResponse();
} catch (error) {
console.error("AI Error:", error);
return Response.json(
{ error: "Failed to process AI request" },
{ status: 500 },
);
}
};