mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Merge remote-tracking branch 'origin/dev' into llm-mcp-flow
This commit is contained in:
commit
f794bd629a
@ -0,0 +1,5 @@
|
||||
-- SPLIT_STATEMENT_SENTINEL
|
||||
-- SINGLE_STATEMENT_SENTINEL
|
||||
-- RUN_OUTSIDE_TRANSACTION_SENTINEL
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS "ProjectUser_signUpEmailNormalized_recent_idx"
|
||||
ON "ProjectUser"("tenancyId", "isAnonymous", "signUpEmailNormalized", "signedUpAt");
|
||||
@ -332,6 +332,7 @@ model ProjectUser {
|
||||
@@index([tenancyId, createdAt(sort: Desc)], name: "ProjectUser_createdAt_desc")
|
||||
@@index([tenancyId, isAnonymous, signedUpAt(sort: Asc)], name: "ProjectUser_signedUpAt_asc")
|
||||
@@index([tenancyId, isAnonymous, signUpIp, signedUpAt], name: "ProjectUser_signUpIp_recent_idx")
|
||||
@@index([tenancyId, isAnonymous, signUpEmailNormalized, signedUpAt], name: "ProjectUser_signUpEmailNormalized_recent_idx")
|
||||
@@index([tenancyId, isAnonymous, signUpEmailBase, signedUpAt], name: "ProjectUser_signUpEmailBase_recent_idx")
|
||||
@@index([tenancyId, sequenceId], name: "ProjectUser_tenancyId_sequenceId_idx")
|
||||
@@index([shouldUpdateSequenceId, tenancyId], name: "ProjectUser_shouldUpdateSequenceId_idx")
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { logMcpCall } from "@/lib/ai/mcp-logger";
|
||||
import { selectModel } from "@/lib/ai/models";
|
||||
import { getFullSystemPrompt } from "@/lib/ai/prompts";
|
||||
import { reviewMcpCall } from "@/lib/ai/qa-reviewer";
|
||||
import { requestBodySchema } from "@/lib/ai/schema";
|
||||
import { getTools, validateToolNames } from "@/lib/ai/tools";
|
||||
import { getVerifiedQaContext } from "@/lib/ai/verified-qa";
|
||||
import { listManagedProjectIds } from "@/lib/projects";
|
||||
import { SmartResponse } from "@/route-handlers/smart-response";
|
||||
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
|
||||
import { yupMixed, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
|
||||
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
|
||||
import { Json } from "@stackframe/stack-shared/dist/utils/json";
|
||||
import { generateText, ModelMessage, stepCountIs, streamText } from "ai";
|
||||
import { logMcpCall } from "@/lib/ai/mcp-logger";
|
||||
import { reviewMcpCall } from "@/lib/ai/qa-reviewer";
|
||||
import { getVerifiedQaContext } from "@/lib/ai/verified-qa";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { generateText, ModelMessage, stepCountIs, streamText } from "ai";
|
||||
|
||||
export const POST = createSmartRouteHandler({
|
||||
metadata: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { tool } from "ai";
|
||||
import { getEnvVariable, getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { captureError } from "@stackframe/stack-shared/dist/utils/errors";
|
||||
import { tool } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
type DocsToolHttpResult = {
|
||||
@ -123,4 +123,72 @@ export async function createDocsTools() {
|
||||
},
|
||||
}),
|
||||
};
|
||||
return {
|
||||
list_available_docs: tool({
|
||||
description:
|
||||
"Use this tool to learn about what Stack Auth is, available documentation, and see if you can use it for what you're working on. It returns a list of all available Stack Auth Documentation pages.",
|
||||
inputSchema: z.object({}),
|
||||
execute: async () => {
|
||||
return await postDocsToolAction({ action: "list_available_docs" });
|
||||
},
|
||||
}),
|
||||
|
||||
search_docs: tool({
|
||||
description:
|
||||
"Search through all Stack Auth documentation including API docs, guides, and examples. Returns ranked results with snippets and relevance scores.",
|
||||
inputSchema: z.object({
|
||||
search_query: z.string().describe("The search query to find relevant documentation"),
|
||||
result_limit: z.number().optional().describe("Maximum number of results to return (default: 50)"),
|
||||
}),
|
||||
execute: async ({ search_query, result_limit = 50 }) => {
|
||||
return await postDocsToolAction({
|
||||
action: "search_docs",
|
||||
search_query,
|
||||
result_limit,
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
get_docs_by_id: tool({
|
||||
description:
|
||||
"Use this tool to retrieve a specific Stack Auth Documentation page by its ID. It gives you the full content of the page so you can know exactly how to use specific Stack Auth APIs. Whenever using Stack Auth, you should always check the documentation first to have the most up-to-date information. When you write code using Stack Auth documentation you should reference the content you used in your comments.",
|
||||
inputSchema: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
execute: async ({ id }) => {
|
||||
return await postDocsToolAction({ action: "get_docs_by_id", id });
|
||||
},
|
||||
}),
|
||||
|
||||
get_stack_auth_setup_instructions: tool({
|
||||
description:
|
||||
"Use this tool when the user wants to set up authentication in a new project. It provides step-by-step instructions for installing and configuring Stack Auth authentication.",
|
||||
inputSchema: z.object({}),
|
||||
execute: async () => {
|
||||
return await postDocsToolAction({ action: "get_stack_auth_setup_instructions" });
|
||||
},
|
||||
}),
|
||||
|
||||
search: tool({
|
||||
description:
|
||||
"Search for Stack Auth documentation pages.\n\nUse this tool to find documentation pages that contain a specific keyword or phrase.",
|
||||
inputSchema: z.object({
|
||||
query: z.string(),
|
||||
}),
|
||||
execute: async ({ query }) => {
|
||||
return await postDocsToolAction({ action: "search", query });
|
||||
},
|
||||
}),
|
||||
|
||||
fetch: tool({
|
||||
description:
|
||||
"Fetch a particular Stack Auth Documentation page by its ID.\n\nThis tool is identical to `get_docs_by_id`.",
|
||||
inputSchema: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
execute: async ({ id }) => {
|
||||
return await postDocsToolAction({ action: "fetch", id });
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@ -30,14 +30,17 @@ export type SignUpRiskAssessment = {
|
||||
export type SignUpRiskRecentStatsRequest = {
|
||||
signedUpAt: Date,
|
||||
signUpIp: string | null,
|
||||
signUpEmailNormalized: string | null,
|
||||
signUpEmailBase: string | null,
|
||||
recentWindowHours: number,
|
||||
sameIpLimit: number,
|
||||
sameEmailLimit: number,
|
||||
similarEmailLimit: number,
|
||||
};
|
||||
|
||||
export type SignUpRiskRecentStats = {
|
||||
sameIpCount: number,
|
||||
sameEmailCount: number,
|
||||
similarEmailCount: number,
|
||||
};
|
||||
|
||||
@ -64,7 +67,7 @@ async function loadRecentSignUpStats(
|
||||
const schema = await getPrismaSchemaForTenancy(tenancy);
|
||||
const windowStart = new Date(request.signedUpAt.getTime() - request.recentWindowHours * 60 * 60 * 1000);
|
||||
|
||||
const [sameIpRows, similarEmailRows] = await Promise.all([
|
||||
const [sameIpRows, sameEmailRows, similarEmailRows] = await Promise.all([
|
||||
request.signUpIp == null || request.sameIpLimit === 0
|
||||
? []
|
||||
: prisma.$replica().$queryRaw<{ matched: number }[]>`
|
||||
@ -77,6 +80,18 @@ async function loadRecentSignUpStats(
|
||||
LIMIT ${request.sameIpLimit}
|
||||
`,
|
||||
|
||||
request.signUpEmailNormalized == null || request.sameEmailLimit === 0
|
||||
? []
|
||||
: prisma.$replica().$queryRaw<{ matched: number }[]>`
|
||||
SELECT 1 AS "matched"
|
||||
FROM ${sqlQuoteIdent(schema)}."ProjectUser"
|
||||
WHERE "tenancyId" = ${tenancy.id}::UUID
|
||||
AND "isAnonymous" = false
|
||||
AND "signedUpAt" >= ${windowStart}
|
||||
AND "signUpEmailNormalized" = ${request.signUpEmailNormalized}
|
||||
LIMIT ${request.sameEmailLimit}
|
||||
`,
|
||||
|
||||
request.signUpEmailBase == null || request.similarEmailLimit === 0
|
||||
? []
|
||||
: prisma.$replica().$queryRaw<{ matched: number }[]>`
|
||||
@ -92,6 +107,7 @@ async function loadRecentSignUpStats(
|
||||
|
||||
return {
|
||||
sameIpCount: sameIpRows.length,
|
||||
sameEmailCount: sameEmailRows.length,
|
||||
similarEmailCount: similarEmailRows.length,
|
||||
};
|
||||
}
|
||||
@ -144,7 +160,7 @@ import.meta.vitest?.test("loaded private sign-up risk engine can calculate score
|
||||
turnstileAssessment: { status: "ok" },
|
||||
}, {
|
||||
checkPrimaryEmailRisk: async () => ({ emailableScore: null }),
|
||||
loadRecentSignUpStats: async () => ({ sameIpCount: 0, similarEmailCount: 0 }),
|
||||
loadRecentSignUpStats: async () => ({ sameIpCount: 0, sameEmailCount: 0, similarEmailCount: 0 }),
|
||||
});
|
||||
|
||||
expect(assessment).toMatchInlineSnapshot(`
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit e4f32c675a640f40ffc19b1013ff46fc633d2438
|
||||
Subproject commit 576f383b69a9593a9cff8d755c64c810aeeae239
|
||||
@ -94,12 +94,15 @@ const handler = createMcpHandler(
|
||||
{
|
||||
capabilities: {
|
||||
tools: {
|
||||
ask_stack_auth: {
|
||||
ask_stack_auth: {
|
||||
description:
|
||||
"Ask the Stack Auth documentation assistant any question about Stack Auth (setup, APIs, SDKs, configuration, troubleshooting).",
|
||||
"Ask the Stack Auth documentation assistant any question about Stack Auth (setup, APIs, SDKs, configuration, troubleshooting).",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
question: {
|
||||
question: {
|
||||
type: "string",
|
||||
description: "The full question to ask about Stack Auth.",
|
||||
@ -130,6 +133,7 @@ const handler = createMcpHandler(
|
||||
basePath: "/api/internal",
|
||||
verboseLogs: true,
|
||||
maxDuration: 120,
|
||||
maxDuration: 120,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user