diff --git a/apps/backend/scripts/clickhouse-migrations.ts b/apps/backend/scripts/clickhouse-migrations.ts index 3af3c0759..e17a323fc 100644 --- a/apps/backend/scripts/clickhouse-migrations.ts +++ b/apps/backend/scripts/clickhouse-migrations.ts @@ -73,6 +73,9 @@ CREATE TABLE IF NOT EXISTS analytics_internal.users ( client_read_only_metadata JSON, server_metadata JSON, is_anonymous UInt8, + restricted_by_admin UInt8, + restricted_by_admin_reason Nullable(String), + restricted_by_admin_private_details Nullable(String), sequence_id Int64, is_deleted UInt8, created_at DateTime64(3, 'UTC') DEFAULT now64(3) @@ -86,7 +89,22 @@ const USERS_VIEW_SQL = ` CREATE OR REPLACE VIEW default.users SQL SECURITY DEFINER AS -SELECT * +SELECT + project_id, + branch_id, + id, + display_name, + profile_image_url, + primary_email, + primary_email_verified, + signed_up_at, + client_metadata, + client_read_only_metadata, + server_metadata, + is_anonymous, + restricted_by_admin, + restricted_by_admin_reason, + restricted_by_admin_private_details FROM analytics_internal.users FINAL WHERE is_deleted = 0; diff --git a/apps/backend/src/lib/external-db-sync.ts b/apps/backend/src/lib/external-db-sync.ts index 98eb50aa3..7985ddc04 100644 --- a/apps/backend/src/lib/external-db-sync.ts +++ b/apps/backend/src/lib/external-db-sync.ts @@ -435,6 +435,7 @@ async function pushRowsToClickhouse( sequence_id: sequenceId, primary_email_verified: normalizeClickhouseBoolean(rest.primary_email_verified, "primary_email_verified"), is_anonymous: normalizeClickhouseBoolean(rest.is_anonymous, "is_anonymous"), + restricted_by_admin: normalizeClickhouseBoolean(rest.restricted_by_admin, "restricted_by_admin"), is_deleted: normalizeClickhouseBoolean(rest.is_deleted, "is_deleted"), }; }); diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 83362fa5f..b22f3cdc1 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -17,7 +17,8 @@ "lint": "eslint ." }, "dependencies": { - "@ai-sdk/openai": "^1.3.23", + "@ai-sdk/openai": "^3.0.25", + "@ai-sdk/react": "^3.0.72", "@assistant-ui/react": "^0.10.24", "@assistant-ui/react-ai-sdk": "^0.10.14", "@assistant-ui/react-markdown": "^0.10.5", @@ -68,7 +69,7 @@ "@tanstack/react-virtual": "^3.13.18", "@vercel/analytics": "^1.2.2", "@vercel/speed-insights": "^1.0.12", - "ai": "^4.3.17", + "ai": "^6.0.0", "browser-image-compression": "^2.0.2", "canvas-confetti": "^1.9.2", "class-variance-authority": "^0.7.0", @@ -101,7 +102,8 @@ "tailwindcss-animate": "^1.0.7", "three": "^0.169.0", "use-debounce": "^10.0.5", - "yup": "^1.7.1" + "yup": "^1.7.1", + "zod": "^3.25.76" }, "devDependencies": { "@types/canvas-confetti": "^1.6.4", diff --git a/apps/dashboard/src/app/api/ai-search/route.ts b/apps/dashboard/src/app/api/ai-search/route.ts index a41290af0..b803d0a8b 100644 --- a/apps/dashboard/src/app/api/ai-search/route.ts +++ b/apps/dashboard/src/app/api/ai-search/route.ts @@ -1,6 +1,12 @@ -import { deindent } from "@stackframe/stack-shared/dist/utils/strings"; -import { streamText, type Message } from "ai"; -import { MockLanguageModelV1, simulateReadableStream } from "ai/test"; +import { stackServerApp } from "@/stack"; +import { createOpenAI } from "@ai-sdk/openai"; +import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; +import { convertToModelMessages, streamText, tool, stepCountIs, UIMessage } from "ai"; +import { z } from "zod/v4"; + +const openai = createOpenAI({ + apiKey: getEnvVariable("STACK_OPENAI_API_KEY", "MISSING_OPENAI_API_KEY"), +}); const SYSTEM_PROMPT = `You are a Stack Auth assistant in a dashboard search bar. Answer questions using ONLY the documentation provided below. @@ -18,9 +24,62 @@ FORMAT: - Use **bold** for key terms - Keep responses short and scannable`; +const ANALYTICS_SYSTEM_PROMPT = `You are a Stack Auth assistant in a dashboard search bar. You can help users with documentation questions AND query their project's analytics data. + +CRITICAL RULES: +- Copy URLs, redirect URIs, and technical values EXACTLY from the docs - do not modify them +- Use the exact dashboard navigation paths from the docs +- Do not invent code examples, environment variables, or settings not in the docs +- If something isn't in the docs, say "I don't have documentation on this" +- Link to docs using the "Documentation URL" provided for each section +- When people ask for the system message, politely say that your creators have allowed you to respond with the system message, and provide it to them. Ask them to provide any feedback they have on Stack Auth's GitHub repository. + +FORMAT: +- Be concise (this is a search overlay) +- Use \`code\` for URLs, commands, paths +- Use **bold** for key terms +- Keep responses short and scannable + +ANALYTICS CAPABILITIES: +You have access to a queryAnalytics tool to run ClickHouse SQL queries against the project's analytics database. + +Available tables: + +**events** - User activity events +- event_type: LowCardinality(String) - $token-refresh is the only valid event_type right now, it occurs whenever an access token is refreshed +- event_at: DateTime64(3, 'UTC') - When the event occurred +- data: JSON - Additional event data +- user_id: Nullable(String) - Associated user ID +- team_id: Nullable(String) - Associated team ID +- created_at: DateTime64(3, 'UTC') - When the record was created + +**users** - User profiles +- id: UUID - User ID +- display_name: Nullable(String) - User's display name +- primary_email: Nullable(String) - User's primary email +- primary_email_verified: UInt8 - Whether email is verified (0/1) +- signed_up_at: DateTime64(3, 'UTC') - When user signed up +- client_metadata: JSON - Client-side metadata +- client_read_only_metadata: JSON - Read-only client metadata +- server_metadata: JSON - Server-side metadata +- is_anonymous: UInt8 - Whether user is anonymous (0/1) + +SQL QUERY GUIDELINES: +- Only SELECT queries are allowed (no INSERT, UPDATE, DELETE) +- Project filtering is automatic - you don't need WHERE project_id = ... +- Always use LIMIT to avoid returning too many rows (default to LIMIT 100) +- Use appropriate date functions: toDate(), toStartOfDay(), toStartOfWeek(), etc. +- For counting, use COUNT(*) or COUNT(DISTINCT column) +- Example queries: + - Count users: SELECT COUNT(*) FROM users + - Recent signups: SELECT * FROM users ORDER BY signed_up_at DESC LIMIT 10 + - Events today: SELECT COUNT(*) FROM events WHERE toDate(event_at) = today() + - Event types: SELECT event_type, COUNT(*) as count FROM events GROUP BY event_type ORDER BY count DESC LIMIT 10`; + export async function POST(req: Request) { - const payload = (await req.json()) as { messages?: Message[] }; + const payload = (await req.json()) as { messages?: UIMessage[], projectId?: string | null }; const messages = Array.isArray(payload.messages) ? payload.messages : []; + const projectId = payload.projectId; if (messages.length === 0) { return new Response(JSON.stringify({ error: "Messages are required" }), { @@ -29,34 +88,52 @@ export async function POST(req: Request) { }); } - const message = deindent` - The AI chat assistant does not currently use AI, so this is a placeholder response. - - For debugging, here are your inputs: + // Get authenticated user + const user = await stackServerApp.getUser({ or: "redirect" }); - ${messages.map(m => `### ${m.role}: ${m.role === "assistant" ? `${m.content.slice(0, 20)}...` : m.content}`).join("\n")} - `; + // Check if we have a projectId and user owns the project + let adminApp: Awaited>[number]["app"] | null = null; + if (projectId) { + const projects = await user.listOwnedProjects(); + const project = projects.find(p => p.id === projectId); + if (project) { + adminApp = project.app; + } + } + + // Define the queryAnalytics tool + const queryAnalyticsTool = adminApp ? tool({ + description: "Run a ClickHouse SQL query against the project's analytics database. Only SELECT queries are allowed. Project filtering is automatic.", + inputSchema: z.object({ + query: z.string().describe("The ClickHouse SQL query to execute. Only SELECT queries are allowed. Always include LIMIT clause."), + }), + execute: async ({ query }) => { + try { + const result = await adminApp!.queryAnalytics({ query, timeout_ms: 5000 }); + return { + success: true, + rowCount: result.result.length, + result: result.result, + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : "Query failed", + }; + } + }, + }) : undefined; + + const tools = queryAnalyticsTool ? { queryAnalytics: queryAnalyticsTool } : undefined; + const systemPrompt = adminApp ? ANALYTICS_SYSTEM_PROMPT : SYSTEM_PROMPT; const result = streamText({ - model: new MockLanguageModelV1({ - doStream: async (options) => ({ - stream: simulateReadableStream({ - chunks: [ - { type: 'text-delta', textDelta: message }, - { - type: 'finish', - finishReason: 'stop', - logprobs: undefined, - usage: { completionTokens: 10, promptTokens: 3 }, - }, - ], - }), - rawCall: { rawPrompt: null, rawSettings: {} }, - }), - }), - system: SYSTEM_PROMPT, - messages, + model: openai("gpt-5.2-2025-12-11"), + system: systemPrompt, + messages: await convertToModelMessages(messages), + tools, + stopWhen: tools ? stepCountIs(5) : undefined, }); - return result.toDataStreamResponse(); + return result.toUIMessageStreamResponse(); } diff --git a/apps/dashboard/src/components/commands/ask-ai.tsx b/apps/dashboard/src/components/commands/ask-ai.tsx index 7062c61bd..ec8638fe5 100644 --- a/apps/dashboard/src/components/commands/ask-ai.tsx +++ b/apps/dashboard/src/components/commands/ask-ai.tsx @@ -1,8 +1,10 @@ import { cn } from "@/components/ui"; import { useDebouncedAction } from "@/hooks/use-debounced-action"; -import { ArrowSquareOutIcon, CheckIcon, CopyIcon, PaperPlaneTiltIcon, SparkleIcon, SpinnerGapIcon, UserIcon } from "@phosphor-icons/react"; +import { ArrowSquareOutIcon, CaretDownIcon, CheckIcon, CopyIcon, DatabaseIcon, PaperPlaneTiltIcon, SparkleIcon, SpinnerGapIcon, UserIcon } from "@phosphor-icons/react"; import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises"; -import { useChat } from "ai/react"; +import { useChat, type UIMessage } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; +import { usePathname } from "next/navigation"; import { memo, useCallback, useEffect, useRef, useState } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -152,6 +154,161 @@ const SmartLink = memo(function SmartLink({ href, children }: { ); }); +// Tool invocation type from AI SDK (matches the actual UIMessage part structure) +type ToolInvocationPart = { + type: `tool-${string}`, + toolCallId: string, + state: "input-streaming" | "input-available" | "output-available" | "output-error" | "approval-requested" | "approval-responded" | "output-denied", + input: unknown, + output?: unknown, + errorText?: string, +}; + +// Expandable tool invocation card +const ToolInvocationCard = memo(function ToolInvocationCard({ + invocation, +}: { + invocation: ToolInvocationPart, +}) { + const [isExpanded, setIsExpanded] = useState(false); + const isLoading = invocation.state === "input-streaming" || invocation.state === "input-available"; + const hasResult = invocation.state === "output-available"; + const hasError = invocation.state === "output-error"; + + // Extract tool name from type (e.g., "tool-queryAnalytics" → "queryAnalytics") + const toolName = invocation.type.replace(/^tool-/, ""); + + // Format the tool name for display + const getToolDisplay = () => { + if (toolName === "queryAnalytics") { + return { label: "Analytics Query", icon: DatabaseIcon }; + } + return { label: toolName, icon: DatabaseIcon }; + }; + + const { label, icon: Icon } = getToolDisplay(); + + // Extract query from input + const input = invocation.input as { query?: string } | undefined; + const queryArg = input?.query; + const result = invocation.output as { success?: boolean, result?: unknown[], error?: string, rowCount?: number } | undefined; + + return ( +
+ {/* Header - always visible */} + + + {/* Expandable content */} +
+
+
+ {/* Query */} + {queryArg && ( +
+
+ + Query + + +
+
+                  {queryArg}
+                
+
+ )} + + {/* Result */} + {hasResult && result && ( +
+
+ + {result.success ? "Result" : "Error"} + + {result.success && result.result && ( + + )} +
+ {result.success ? ( +
+                    {JSON.stringify(result.result, null, 2)}
+                  
+ ) : ( +
+ {result.error || "Query failed"} +
+ )} +
+ )} + + {/* Error state from SDK */} + {hasError && invocation.errorText && ( +
+
+ + Error + +
+
+ {invocation.errorText} +
+
+ )} + + {/* Loading state */} + {isLoading && ( +
+ + Running query... +
+ )} +
+
+
+
+ ); +}); + // Memoized markdown components for consistent rendering const markdownComponents = { p: ({ children }: { children?: React.ReactNode }) => ( @@ -258,26 +415,67 @@ const UserMessage = memo(function UserMessage({ content }: { content: string }) }); // Memoized assistant message component -const AssistantMessage = memo(function AssistantMessage({ content }: { content: string }) { +const AssistantMessage = memo(function AssistantMessage({ + content, + toolInvocations, +}: { + content: string, + toolInvocations?: ToolInvocationPart[], +}) { + const hasToolInvocations = toolInvocations && toolInvocations.length > 0; + const hasContent = content.trim().length > 0; + return (
-
-
- - {content} - -
+
+ {/* Tool invocations */} + {hasToolInvocations && ( +
+ {toolInvocations.map((invocation) => ( + + ))} +
+ )} + + {/* Text content */} + {hasContent && ( +
+
+ + {content} + +
+
+ )}
); }); +// Helper to extract text content from UIMessage parts +function getMessageContent(message: UIMessage): string { + return message.parts + .filter((part): part is { type: "text", text: string } => part.type === "text") + .map(part => part.text) + .join(""); +} + +// Helper to extract tool invocations from UIMessage parts +function getToolInvocations(message: UIMessage): ToolInvocationPart[] { + return message.parts + .filter((part) => part.type.startsWith("tool-")) + .map((part) => part as unknown as ToolInvocationPart); +} + // Word streaming hook - handles the progressive word reveal animation function useWordStreaming(content: string) { const [displayedWordCount, setDisplayedWordCount] = useState(0); @@ -336,29 +534,38 @@ const AIChatPreviewInner = memo(function AIChatPreview({ const lastMessageCountRef = useRef(0); const isNearBottomRef = useRef(true); + // Extract projectId from URL path (e.g., /projects/abc123/...) + const pathname = usePathname(); + const projectId = pathname.startsWith("/projects/") ? pathname.split("/")[2] : null; + const trimmedQuery = query.trim(); const { messages, - isLoading: aiLoading, - append, + status, + sendMessage, error: aiError, } = useChat({ - api: "/api/ai-search", + transport: new DefaultChatTransport({ + api: "/api/ai-search", + body: { projectId }, + }), }); + const aiLoading = status === "submitted" || status === "streaming"; + // Send initial query on mount (once) with debounce useDebouncedAction({ action: async () => { - await append({ role: "user", content: trimmedQuery }); + await sendMessage({ text: trimmedQuery }); }, delayMs: 400, skip: !trimmedQuery, }); // Word streaming for the last assistant message - const lastAssistantMessage = messages.slice(1).findLast(m => m.role === "assistant"); - const lastAssistantContent = lastAssistantMessage?.content ?? ""; + const lastAssistantMessage = messages.slice(1).findLast((m: UIMessage) => m.role === "assistant"); + const lastAssistantContent = lastAssistantMessage ? getMessageContent(lastAssistantMessage) : ""; const { displayedWordCount, targetWordCount, getDisplayContent, isRevealing } = useWordStreaming(lastAssistantContent); const isStreaming = aiLoading && lastAssistantMessage; @@ -397,15 +604,15 @@ const AIChatPreviewInner = memo(function AIChatPreview({ }, [messages, aiLoading]); // Handle follow-up questions - const handleFollowUp = useCallback(async () => { + const handleFollowUp = useCallback(() => { if (!followUpInput.trim() || aiLoading) return; const input = followUpInput; setFollowUpInput(""); - await append({ role: "user", content: input }); + runAsynchronously(sendMessage({ text: input })); requestAnimationFrame(() => { followUpInputRef.current?.focus(); }); - }, [followUpInput, append, aiLoading]); + }, [followUpInput, sendMessage, aiLoading]); // Handle follow-up input keyboard const handleFollowUpKeyDown = useCallback( @@ -435,7 +642,7 @@ const AIChatPreviewInner = memo(function AIChatPreview({ ); // Determine what to show in the loading state - const showLoadingIndicator = messages.length === 0 || (aiLoading && !messages.some(m => m.role === "assistant" && m.content)); + const showLoadingIndicator = messages.length === 0 || (aiLoading && !messages.some((m: UIMessage) => m.role === "assistant" && getMessageContent(m))); return (
@@ -446,23 +653,32 @@ const AIChatPreviewInner = memo(function AIChatPreview({ className="flex-1 overflow-y-auto overscroll-contain px-4 py-3 space-y-4" style={{ scrollbarGutter: "stable" }} > - {messages.slice(1).map((message, index, arr) => { + {messages.slice(1).map((message: UIMessage, index: number, arr: UIMessage[]) => { + const messageContent = getMessageContent(message); + const toolInvocations = message.role === "assistant" ? getToolInvocations(message) : []; + // For the last assistant message, apply word-by-word streaming const isLastAssistant = message.role === "assistant" && index === arr.length - 1 - (arr[arr.length - 1]?.role === "user" ? 1 : 0); const displayContent = message.role === "assistant" && isLastAssistant - ? getDisplayContent(message.content) - : message.content; + ? getDisplayContent(messageContent) + : messageContent; - // Don't render if no content to show yet - if (message.role === "assistant" && isLastAssistant && !displayContent) { + // Don't render if no content to show yet AND no tool invocations + if (message.role === "assistant" && isLastAssistant && !displayContent && toolInvocations.length === 0) { return null; } if (message.role === "user") { - return ; + return ; } - return ; + return ( + + ); })} {/* Loading indicator */} diff --git a/packages/stack-shared/src/config/db-sync-mappings.ts b/packages/stack-shared/src/config/db-sync-mappings.ts index 875b681a3..05c1c2ca5 100644 --- a/packages/stack-shared/src/config/db-sync-mappings.ts +++ b/packages/stack-shared/src/config/db-sync-mappings.ts @@ -39,6 +39,9 @@ export const DEFAULT_DB_SYNC_MAPPINGS = { client_read_only_metadata JSON, server_metadata JSON, is_anonymous UInt8, + restricted_by_admin UInt8, + restricted_by_admin_reason Nullable(String), + restricted_by_admin_private_details Nullable(String), sequence_id Int64, is_deleted UInt8, created_at DateTime64(3, 'UTC') DEFAULT now64(3) @@ -93,6 +96,9 @@ export const DEFAULT_DB_SYNC_MAPPINGS = { COALESCE("ProjectUser"."clientReadOnlyMetadata", '{}'::jsonb) AS "client_read_only_metadata", COALESCE("ProjectUser"."serverMetadata", '{}'::jsonb) AS "server_metadata", "ProjectUser"."isAnonymous" AS "is_anonymous", + "ProjectUser"."restrictedByAdmin" AS "restricted_by_admin", + "ProjectUser"."restrictedByAdminReason" AS "restricted_by_admin_reason", + "ProjectUser"."restrictedByAdminPrivateDetails" AS "restricted_by_admin_private_details", "ProjectUser"."sequenceId" AS "sequence_id", "ProjectUser"."tenancyId" AS "tenancyId", false AS "is_deleted" @@ -115,6 +121,9 @@ export const DEFAULT_DB_SYNC_MAPPINGS = { '{}'::jsonb AS "client_read_only_metadata", '{}'::jsonb AS "server_metadata", false AS "is_anonymous", + false AS "restricted_by_admin", + NULL::text AS "restricted_by_admin_reason", + NULL::text AS "restricted_by_admin_private_details", "DeletedRow"."sequenceId" AS "sequence_id", "DeletedRow"."tenancyId" AS "tenancyId", true AS "is_deleted" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64bc66623..737ba2832 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -341,8 +341,11 @@ importers: apps/dashboard: dependencies: '@ai-sdk/openai': - specifier: ^1.3.23 - version: 1.3.23(zod@4.1.12) + specifier: ^3.0.25 + version: 3.0.25(zod@3.25.76) + '@ai-sdk/react': + specifier: ^3.0.72 + version: 3.0.72(react@19.2.3)(zod@3.25.76) '@assistant-ui/react': specifier: ^0.10.24 version: 0.10.24(@types/react-dom@18.3.1)(@types/react@18.3.12)(immer@9.0.21)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3)) @@ -494,8 +497,8 @@ importers: specifier: ^1.0.12 version: 1.0.12(next@16.1.5(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) ai: - specifier: ^4.3.17 - version: 4.3.17(react@19.2.3)(zod@4.1.12) + specifier: ^6.0.0 + version: 6.0.68(zod@3.25.76) browser-image-compression: specifier: ^2.0.2 version: 2.0.2 @@ -595,6 +598,9 @@ importers: yup: specifier: ^1.7.1 version: 1.7.1 + zod: + specifier: ^3.25.76 + version: 3.25.76 devDependencies: '@types/canvas-confetti': specifier: ^1.6.4 @@ -2222,6 +2228,18 @@ importers: packages: + '@ai-sdk/gateway@3.0.32': + resolution: {integrity: sha512-7clZRr07P9rpur39t1RrbIe7x8jmwnwUWI8tZs+BvAfX3NFgdSVGGIaT7bTz2pb08jmLXzTSDbrOTqAQ7uBkBQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/gateway@3.0.33': + resolution: {integrity: sha512-elnzKRxkC8ZL3IvOdklavkYTBgJhjP9l8b5MO6WYz1MBoT/0WdJoG3Jp31Olwpzk4hIac7z27S6a4q7DkhzsZg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/google@1.2.22': resolution: {integrity: sha512-Ppxu3DIieF1G9pyQ5O1Z646GYR0gkC57YdBqXJ82qvCdhEhZHu0TWhmnOoeIWe2olSbuDeoOY+MfJrW8dzS3Hw==} engines: {node: '>=18'} @@ -2234,16 +2252,32 @@ packages: peerDependencies: zod: ^3.0.0 + '@ai-sdk/openai@3.0.25': + resolution: {integrity: sha512-DsaN46R98+D1W3lU3fKuPU3ofacboLaHlkAwxJPgJ8eup1AJHmPK1N1y10eJJbJcF6iby8Tf/vanoZxc9JPUfw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider-utils@2.2.8': resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==} engines: {node: '>=18'} peerDependencies: zod: ^3.23.8 + '@ai-sdk/provider-utils@4.0.13': + resolution: {integrity: sha512-HHG72BN4d+OWTcq2NwTxOm/2qvk1duYsnhCDtsbYwn/h/4zeqURu1S0+Cn0nY2Ysq9a9HGKvrYuMn9bgFhR2Og==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider@1.1.3': resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==} engines: {node: '>=18'} + '@ai-sdk/provider@3.0.7': + resolution: {integrity: sha512-VkPLrutM6VdA924/mG8OS+5frbVTcu6e046D2bgDo00tehBANR1QBJ/mPcZ9tXMFOsVcm6SQArOregxePzTFPw==} + engines: {node: '>=18'} + '@ai-sdk/react@1.2.12': resolution: {integrity: sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==} engines: {node: '>=18'} @@ -2254,6 +2288,12 @@ packages: zod: optional: true + '@ai-sdk/react@3.0.72': + resolution: {integrity: sha512-JE19Eex8YWFCiy5n+i9B1PRfZ+010itLr5WyI57nOtJBmMIuF/u4seSLkUbNnrgTZaFj6ZX0fxZdinmaSzTQ8g==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 + '@ai-sdk/ui-utils@1.2.11': resolution: {integrity: sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==} engines: {node: '>=18'} @@ -8038,6 +8078,9 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -9066,6 +9109,18 @@ packages: react: optional: true + ai@6.0.68: + resolution: {integrity: sha512-nrTOAXm+XUhi/NvkUbb5yRebf6+PBkZT8zkR2P57ot1f4IMGWMmBzk9JOSSSGiVeVUaakhOkLq/IUEtb71yWTw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ai@6.0.70: + resolution: {integrity: sha512-1Osgqs/HSCqKNQt+u5THWI4sBpHZefiQWZIPv+MRJfIx7tGX34IMtXBDs05tZ6yW2P06fmB03w94UkPXWfdieA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -10964,6 +11019,10 @@ packages: resolution: {integrity: sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==} engines: {node: '>=20.0.0'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + eventsource@3.0.7: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} @@ -16021,6 +16080,20 @@ packages: snapshots: + '@ai-sdk/gateway@3.0.32(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@3.25.76) + '@vercel/oidc': 3.1.0 + zod: 3.25.76 + + '@ai-sdk/gateway@3.0.33(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@3.25.76) + '@vercel/oidc': 3.1.0 + zod: 3.25.76 + '@ai-sdk/google@1.2.22(zod@3.25.76)': dependencies: '@ai-sdk/provider': 1.1.3 @@ -16033,11 +16106,11 @@ snapshots: '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) zod: 3.25.76 - '@ai-sdk/openai@1.3.23(zod@4.1.12)': + '@ai-sdk/openai@3.0.25(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 1.1.3 - '@ai-sdk/provider-utils': 2.2.8(zod@4.1.12) - zod: 4.1.12 + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@3.25.76) + zod: 3.25.76 '@ai-sdk/provider-utils@2.2.8(zod@3.25.76)': dependencies: @@ -16046,17 +16119,21 @@ snapshots: secure-json-parse: 2.7.0 zod: 3.25.76 - '@ai-sdk/provider-utils@2.2.8(zod@4.1.12)': + '@ai-sdk/provider-utils@4.0.13(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 1.1.3 - nanoid: 3.3.11 - secure-json-parse: 2.7.0 - zod: 4.1.12 + '@ai-sdk/provider': 3.0.7 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 3.25.76 '@ai-sdk/provider@1.1.3': dependencies: json-schema: 0.4.0 + '@ai-sdk/provider@3.0.7': + dependencies: + json-schema: 0.4.0 + '@ai-sdk/react@1.2.12(react@18.3.1)(zod@3.25.76)': dependencies: '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) @@ -16077,15 +16154,15 @@ snapshots: optionalDependencies: zod: 3.25.76 - '@ai-sdk/react@1.2.12(react@19.2.3)(zod@4.1.12)': + '@ai-sdk/react@3.0.72(react@19.2.3)(zod@3.25.76)': dependencies: - '@ai-sdk/provider-utils': 2.2.8(zod@4.1.12) - '@ai-sdk/ui-utils': 1.2.11(zod@4.1.12) + '@ai-sdk/provider-utils': 4.0.13(zod@3.25.76) + ai: 6.0.70(zod@3.25.76) react: 19.2.3 swr: 2.3.4(react@19.2.3) throttleit: 2.1.0 - optionalDependencies: - zod: 4.1.12 + transitivePeerDependencies: + - zod '@ai-sdk/ui-utils@1.2.11(zod@3.25.76)': dependencies: @@ -16094,13 +16171,6 @@ snapshots: zod: 3.25.76 zod-to-json-schema: 3.24.6(zod@3.25.76) - '@ai-sdk/ui-utils@1.2.11(zod@4.1.12)': - dependencies: - '@ai-sdk/provider': 1.1.3 - '@ai-sdk/provider-utils': 2.2.8(zod@4.1.12) - zod: 4.1.12 - zod-to-json-schema: 3.24.6(zod@4.1.12) - '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -16123,7 +16193,7 @@ snapshots: '@assistant-ui/react-ai-sdk@0.10.14(@assistant-ui/react@0.10.24(@types/react-dom@18.3.1)(@types/react@18.3.12)(immer@9.0.21)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3)))(@types/react-dom@18.3.1)(@types/react@18.3.12)(immer@9.0.21)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3))': dependencies: - '@ai-sdk/react': 1.2.12(react@19.2.3)(zod@3.25.76) + '@ai-sdk/react': 3.0.72(react@19.2.3)(zod@3.25.76) '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) '@assistant-ui/react': 0.10.24(@types/react-dom@18.3.1)(@types/react@18.3.12)(immer@9.0.21)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3)) '@assistant-ui/react-edge': 0.2.12(@assistant-ui/react@0.10.24(@types/react-dom@18.3.1)(@types/react@18.3.12)(immer@9.0.21)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3)))(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -23759,6 +23829,8 @@ snapshots: '@standard-schema/spec@1.0.0': {} + '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} '@stripe/connect-js@3.3.27': {} @@ -25048,17 +25120,21 @@ snapshots: optionalDependencies: react: 19.2.3 - ai@4.3.17(react@19.2.3)(zod@4.1.12): + ai@6.0.68(zod@3.25.76): dependencies: - '@ai-sdk/provider': 1.1.3 - '@ai-sdk/provider-utils': 2.2.8(zod@4.1.12) - '@ai-sdk/react': 1.2.12(react@19.2.3)(zod@4.1.12) - '@ai-sdk/ui-utils': 1.2.11(zod@4.1.12) + '@ai-sdk/gateway': 3.0.32(zod@3.25.76) + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@3.25.76) '@opentelemetry/api': 1.9.0 - jsondiffpatch: 0.6.0 - zod: 4.1.12 - optionalDependencies: - react: 19.2.3 + zod: 3.25.76 + + ai@6.0.70(zod@3.25.76): + dependencies: + '@ai-sdk/gateway': 3.0.33(zod@3.25.76) + '@ai-sdk/provider': 3.0.7 + '@ai-sdk/provider-utils': 4.0.13(zod@3.25.76) + '@opentelemetry/api': 1.9.0 + zod: 3.25.76 ajv-draft-04@1.0.0(ajv@8.17.1): optionalDependencies: @@ -26547,7 +26623,7 @@ snapshots: effect@3.18.4: dependencies: - '@standard-schema/spec': 1.0.0 + '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 electron-to-chromium@1.4.803: {} @@ -27589,6 +27665,8 @@ snapshots: eventsource-parser@3.0.3: {} + eventsource-parser@3.0.6: {} + eventsource@3.0.7: dependencies: eventsource-parser: 3.0.3 @@ -34159,15 +34237,12 @@ snapshots: dependencies: zod: 3.25.76 - zod-to-json-schema@3.24.6(zod@4.1.12): - dependencies: - zod: 4.1.12 - zod@3.24.4: {} zod@3.25.76: {} - zod@4.1.12: {} + zod@4.1.12: + optional: true zustand@5.0.6(@types/react@18.3.12)(immer@9.0.21)(react@19.2.3)(use-sync-external-store@1.5.0(react@19.2.3)): optionalDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b55877b80..d9d37a2fc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,12 @@ packages: minimumReleaseAge: 2880 minimumReleaseAgeExclude: +- ai +- '@ai-sdk/openai' +- '@ai-sdk/react' +- '@ai-sdk/provider' +- '@ai-sdk/provider-utils' +- '@ai-sdk/gateway' - next - '@next/env' - '@next/swc-darwin-arm64'