From 97aeb76b306f90e99abd768aafe5eaa2b91f0b22 Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Tue, 18 Nov 2025 15:47:47 +1000 Subject: [PATCH] all the things --- .../content/docs/assistant-chat.mdx | 16 +- apps/ui-library/package.json | 4 +- .../assistant/components/assistant-widget.tsx | 577 ++++++------------ .../blocks/assistant/registry-item.json | 1 + .../supabase/functions/chat/deno.json | 9 +- .../supabase/functions/chat/index.ts | 71 ++- .../supabase/functions/mcp-server/deno.json | 7 +- .../supabase/functions/mcp-server/index.ts | 340 ++++++++--- .../functions/mcp-server/lib/supabase.ts | 15 + .../functions/mcp-server/tools/create-task.ts | 98 +++ .../mcp-server/tools/get-user-tasks.ts | 101 +++ .../functions/mcp-server/tools/registry.ts | 6 + .../default/components/ui/popover.tsx | 6 +- .../default/examples/assistant-demo.tsx | 124 ++-- pnpm-lock.yaml | 104 ++-- 15 files changed, 869 insertions(+), 610 deletions(-) create mode 100644 apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/lib/supabase.ts create mode 100644 apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/create-task.ts create mode 100644 apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/get-user-tasks.ts create mode 100644 apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/registry.ts diff --git a/apps/ui-library/content/docs/assistant-chat.mdx b/apps/ui-library/content/docs/assistant-chat.mdx index 7c93fe86ab8..063589a6751 100644 --- a/apps/ui-library/content/docs/assistant-chat.mdx +++ b/apps/ui-library/content/docs/assistant-chat.mdx @@ -1,6 +1,6 @@ --- title: Assistant -description: AI assistant widget that streams database-aware answers through Supabase Edge Functions and the Model Context Protocol (MCP). +description: Add an AI Assistant to your product that can read and write to your database using user defined tools that respect your RLS policies. --- @@ -36,13 +36,13 @@ The bundle contains four key pieces: ## Environment variables -| Key | Where it is used | Notes | -| --- | ---------------- | ----- | -| `NEXT_PUBLIC_SUPABASE_URL` | Next.js client + Edge Functions | Base URL of your Supabase project. | -| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Next.js client | Anonymous key used by the browser client. | -| `NEXT_PUBLIC_ASSISTANT_CHAT_URL` | Next.js client | Optional override for the chat Edge Function endpoint. Defaults to `${NEXT_PUBLIC_SUPABASE_URL}/functions/v1/chat`. | -| `OPENAI_API_KEY` | `supabase/functions/chat` | Injected into the Edge Function to talk to your LLM provider. | -| `MCP_SERVER_URL` | `supabase/functions/chat` and `supabase/functions/mcp-server` | Used by the chat handler to find the MCP server endpoint. | +| Key | Where it is used | Notes | +| -------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `NEXT_PUBLIC_SUPABASE_URL` | Next.js client + Edge Functions | Base URL of your Supabase project. | +| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Next.js client | Anonymous key used by the browser client. | +| `NEXT_PUBLIC_ASSISTANT_CHAT_URL` | Next.js client | Optional override for the chat Edge Function endpoint. Defaults to `${NEXT_PUBLIC_SUPABASE_URL}/functions/v1/chat`. | +| `OPENAI_API_KEY` | `supabase/functions/chat` | Injected into the Edge Function to talk to your LLM provider. | +| `MCP_SERVER_URL` | `supabase/functions/chat` and `supabase/functions/mcp-server` | Used by the chat handler to find the MCP server endpoint. | Store these values in `.env.local` for the app and in a `.env` file that you pass to `supabase functions serve`. diff --git a/apps/ui-library/package.json b/apps/ui-library/package.json index d768d8eb597..b4491e459f2 100644 --- a/apps/ui-library/package.json +++ b/apps/ui-library/package.json @@ -17,7 +17,7 @@ "typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json" }, "dependencies": { - "@ai-sdk/react": "2.0.45", + "@ai-sdk/react": "^2.0.68", "@hookform/resolvers": "^3.1.1", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-accordion": "*", @@ -54,7 +54,7 @@ "@supabase/vue-blocks": "workspace:*", "@tanstack/react-query": "^5.83.0", "@xyflow/react": "^12.9.2", - "ai": "^5.0.90", + "ai": "^5.0.93", "axios": "^1.12.0", "class-variance-authority": "*", "cmdk": "^1.0.0", diff --git a/apps/ui-library/registry/default/blocks/assistant/components/assistant-widget.tsx b/apps/ui-library/registry/default/blocks/assistant/components/assistant-widget.tsx index de61e6763fe..3747ba150d8 100644 --- a/apps/ui-library/registry/default/blocks/assistant/components/assistant-widget.tsx +++ b/apps/ui-library/registry/default/blocks/assistant/components/assistant-widget.tsx @@ -1,6 +1,4 @@ 'use client' - -import { cn } from '@/lib/utils' import { createClient } from '@/registry/default/blocks/assistant/lib/supabase/client' import { Conversation, @@ -9,12 +7,6 @@ import { } from '@/registry/default/components/ai-elements/conversation' import { Message, - MessageBranch, - MessageBranchContent, - MessageBranchNext, - MessageBranchPage, - MessageBranchPrevious, - MessageBranchSelector, MessageContent, MessageResponse, } from '@/registry/default/components/ai-elements/message' @@ -27,52 +19,29 @@ import { ModelSelectorItem, ModelSelectorList, ModelSelectorLogo, - ModelSelectorLogoGroup, ModelSelectorName, ModelSelectorTrigger, } from '@/registry/default/components/ai-elements/model-selector' import { PromptInput, - PromptInputActionAddAttachments, - PromptInputActionMenu, - PromptInputActionMenuContent, - PromptInputActionMenuTrigger, - PromptInputAttachment, - PromptInputAttachments, PromptInputBody, PromptInputButton, PromptInputFooter, - PromptInputHeader, type PromptInputMessage, PromptInputSubmit, PromptInputTextarea, PromptInputTools, } from '@/registry/default/components/ai-elements/prompt-input' -import { - Reasoning, - ReasoningContent, - ReasoningTrigger, -} from '@/registry/default/components/ai-elements/reasoning' -import { - Source, - Sources, - SourcesContent, - SourcesTrigger, -} from '@/registry/default/components/ai-elements/sources' import { Suggestion, Suggestions } from '@/registry/default/components/ai-elements/suggestion' import { Button } from '@/registry/default/components/ui/button' +import { Popover, PopoverContent, PopoverTrigger } from '@/registry/default/components/ui/popover' import { useChat } from '@ai-sdk/react' import { DefaultChatTransport } from 'ai' -import { GlobeIcon, MessageSquare, MicIcon, Minimize2, X } from 'lucide-react' -import { useCallback, useMemo, useState } from 'react' +import { CheckIcon, MessageCircle } from 'lucide-react' +import { useCallback, useState } from 'react' import { toast } from 'sonner' -const suggestions = [ - 'Show me my recent orders', - 'How many active users do we have?', - 'List all tasks due this week', - "Summarize today's signups", -] +const CHAT_API_URL = process.env.NEXT_PUBLIC_CHAT_API_URL const models = [ { @@ -89,6 +58,13 @@ const models = [ chefSlug: 'openai', providers: ['openai', 'azure'], }, + { + id: 'claude-opus-4-20250514', + name: 'Claude 4 Opus', + chef: 'Anthropic', + chefSlug: 'anthropic', + providers: ['anthropic', 'azure', 'google', 'amazon-bedrock'], + }, { id: 'claude-sonnet-4-20250514', name: 'Claude 4 Sonnet', @@ -105,397 +81,218 @@ const models = [ }, ] -type TextPart = { - type: 'text' - text: string +const suggestions = [ + 'What are the latest trends in AI?', + 'How does machine learning work?', + 'Explain quantum computing', + 'Best practices for React development', + 'Tell me about TypeScript benefits', + 'How to optimize database queries?', + 'What is the difference between SQL and NoSQL?', + 'Explain cloud computing basics', +] + +interface AssistantWidgetProps { + /** + * Override the trigger positioning if you need to embed the widget in a scoped container. + */ + triggerClassName?: string } -type SourcePart = { - type: 'source-url' - url: string - title?: string -} +export const AssistantWidget = ({ + triggerClassName = 'fixed bottom-6 right-6', +}: AssistantWidgetProps = {}) => { + const [model, setModel] = useState(models[0].id) + const [modelSelectorOpen, setModelSelectorOpen] = useState(false) + const [text, setText] = useState('') + const [useWebSearch, setUseWebSearch] = useState(false) + const [useMicrophone, setUseMicrophone] = useState(false) -type ReasoningPart = { - type: 'reasoning' - text: string -} - -type FormattedMessage = { - key: string - from: 'user' | 'assistant' - sources?: { href: string; title: string }[] - versions: { id: string; content: string }[] - reasoning?: { content: string; duration?: number } -} - -const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/$/, '') -const DEFAULT_CHAT_API = SUPABASE_URL ? `${SUPABASE_URL}/functions/v1/chat` : '/functions/v1/chat' -const CHAT_API_URL = 'https://idglwaxxhycmeyjvbbbr.supabase.co/functions/v1/chat' - -export function AssistantWidget() { - const supabase = useMemo(() => createClient(), []) const getAuthHeaders = useCallback(async () => { - let { + const supabase = createClient() + const { data: { session }, - error: sessionError, + error, } = await supabase.auth.getSession() - if (sessionError) { - throw sessionError + if (error) { + throw error } if (!session) { - const { data, error } = await supabase.auth.signInAnonymously() - - if (error || !data.session) { - throw error ?? new Error('Unable to authenticate anonymously.') - } - - session = data.session + throw new Error('No active session found. Please sign in.') } return { Authorization: `Bearer ${session.access_token}`, } - }, [supabase]) - - const [isOpen, setIsOpen] = useState(false) - const [isMinimized, setIsMinimized] = useState(false) - const [text, setText] = useState('') - const [model, setModel] = useState(models[0].id) - const [modelSelectorOpen, setModelSelectorOpen] = useState(false) - const [useWebSearch, setUseWebSearch] = useState(false) - const [useMicrophone, setUseMicrophone] = useState(false) + }, []) const { messages, sendMessage, status, error } = useChat({ transport: new DefaultChatTransport({ api: CHAT_API_URL, + headers: async () => { + try { + return await getAuthHeaders() + } catch (error) { + console.error('Authentication error:', error) + toast.error('Failed to authenticate') + return {} + } + }, + body: () => ({ + model, + useWebSearch, + }), }), - onError: (chatError) => { + onError: (error) => { + console.error('Chat error:', error) toast.error('Failed to send message', { - description: chatError.message, + description: error.message, }) }, + onFinish: () => { + // Successfully finished streaming + }, }) - const selectedModelData = models.find((entry) => entry.id === model) + const selectedModelData = models.find((m) => m.id === model) - const formattedMessages = useMemo(() => { - return messages.map((message) => { - const parts = message.parts ?? [] + const handleSubmit = useCallback( + (message: PromptInputMessage) => { + const hasText = Boolean(message.text) + const hasAttachments = Boolean(message.files?.length) - const textParts = - parts.filter((part): part is TextPart => part?.type === 'text') ?? - (Array.isArray(message.content) - ? message.content.filter( - (part): part is TextPart => typeof part === 'object' && part.type === 'text' - ) - : []) - - const versions = - textParts.length > 0 - ? textParts.map((part, index) => ({ - id: `${message.id}-${index}`, - content: part.text, - })) - : [ - { - id: `${message.id}-content`, - content: - typeof message.content === 'string' - ? message.content - : message.content - ?.map((part) => - typeof part === 'string' ? part : part.type === 'text' ? part.text : '' - ) - .join('') - .trim() ?? '', - }, - ] - - const sources = parts - .filter((part): part is SourcePart => part?.type === 'source-url') - .map((part) => ({ - href: part.url, - title: part.title ?? part.url, - })) - - const reasoningPart = parts.find((part): part is ReasoningPart => part?.type === 'reasoning') - - return { - key: message.id, - from: message.role === 'assistant' ? 'assistant' : 'user', - sources, - versions, - reasoning: reasoningPart - ? { - content: reasoningPart.text, - } - : undefined, + if (!(hasText || hasAttachments)) { + return } - }) - }, [messages]) - const handleSubmit = async (message: PromptInputMessage) => { - const hasText = Boolean(message.text?.trim()) - const hasAttachments = Boolean(message.files?.length) + if (message.files?.length) { + toast.success('Files attached', { + description: `${message.files.length} file(s) attached to message`, + }) + } - if (!(hasText || hasAttachments) || status === 'streaming') { - return - } - - if (message.files?.length) { - toast.success('Files attached', { - description: `${message.files.length} file(s) attached to message`, - }) - } - - try { - const headers = await getAuthHeaders() - await sendMessage( - { - text: message.text?.trim() ?? 'Sent with attachments', - files: message.files, - }, - { - headers, - body: { - model, - webSearch: useWebSearch, - }, - } - ) + sendMessage({ text: message.text || 'Sent with attachments' }) setText('') - } catch (sendError) { - toast.error('Failed to send message', { - description: sendError instanceof Error ? sendError.message : 'Unknown error occurred.', - }) - } - } + }, + [sendMessage] + ) - const handleSuggestionClick = async (suggestion: string) => { - if (status === 'streaming' || status === 'submitted') return - try { - const headers = await getAuthHeaders() - await sendMessage( - { text: suggestion }, - { - headers, - body: { model, webSearch: useWebSearch }, - } - ) - } catch (sendError) { - toast.error('Failed to send message', { - description: sendError instanceof Error ? sendError.message : 'Unknown error occurred.', - }) - } - } - - if (!isOpen) { - return ( - - ) - } - - const submitDisabled = (!text.trim() && status !== 'streaming') || status === 'streaming' + const handleSuggestionClick = useCallback( + (suggestion: string) => { + sendMessage({ text: suggestion }) + }, + [sendMessage] + ) return ( -
-
-
- -
-

AI Assistant

-

Powered by MCP

-
-
-
- - -
-
+ + + + - {!isMinimized && ( -
-
- - - {formattedMessages.map(({ key, versions, sources, reasoning, from }) => ( - - - {versions.map((version) => ( - -
- {sources && sources.length > 0 && ( - - - - {sources.map((source) => ( - - ))} - - - )} - - {reasoning && ( - - - {reasoning.content} - - )} - - - {version.content} - -
-
+ + + + {messages.map(({ role, parts }, index) => ( + + + {parts.map((part, i) => { + switch (part.type) { + case 'text': + return {part.text} + } + })} + + + ))} + + + + + {suggestions.map((suggestion) => ( + handleSuggestionClick(suggestion)} + suggestion={suggestion} + /> + ))} + +
+ + + setText(event.target.value)} value={text} /> + + + + + + + {selectedModelData?.chefSlug && ( + + )} + {selectedModelData?.name && ( + {selectedModelData.name} + )} + + + + + + No models found. + {['OpenAI', 'Anthropic', 'Google'].map((chef) => ( + + {models + .filter((m) => m.chef === chef) + .map((m) => ( + { + setModel(m.id) + setModelSelectorOpen(false) + }} + value={m.id} + > + + {m.name} + {model === m.id ? ( + + ) : ( +
+ )} + + ))} + ))} - - {versions.length > 1 && ( - - - - - - )} - - ))} - - - -
- -
- - {suggestions.map((suggestion) => ( - handleSuggestionClick(suggestion)} - /> - ))} - -
- - - - {(attachment) => } - - - - setText(event.target.value)} - placeholder="Ask me anything about your Supabase data..." - /> - - - - - - - - - - setUseMicrophone((prev) => !prev)} - variant={useMicrophone ? 'default' : 'ghost'} - type="button" - > - - Microphone - - setUseWebSearch((prev) => !prev)} - variant={useWebSearch ? 'default' : 'ghost'} - type="button" - > - - Search - - - - - {selectedModelData?.chefSlug && ( - - )} - {selectedModelData?.name && ( - {selectedModelData.name} - )} - - - - - - No models found. - {['OpenAI', 'Anthropic', 'Google'].map((chef) => ( - - {models - .filter((entry) => entry.chef === chef) - .map((entry) => ( - { - setModel(entry.id) - setModelSelectorOpen(false) - }} - > - - {entry.name} - - {entry.providers.map((provider) => ( - - ))} - - {model === entry.id ? ( - - Selected - - ) : ( -
- )} - - ))} - - ))} - - - - - - - - {error &&

{error.message}

} -
-
+ + + + + + +
- )} -
+
+ ) } + +export default AssistantWidget diff --git a/apps/ui-library/registry/default/blocks/assistant/registry-item.json b/apps/ui-library/registry/default/blocks/assistant/registry-item.json index 07c31163534..8bd89ccc19a 100644 --- a/apps/ui-library/registry/default/blocks/assistant/registry-item.json +++ b/apps/ui-library/registry/default/blocks/assistant/registry-item.json @@ -6,6 +6,7 @@ "registryDependencies": [ "button", "input", + "popover", "scroll-area", "@ai-elements/conversation", "@ai-elements/message", diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/deno.json b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/deno.json index 3c2f9a89941..98531f3d115 100644 --- a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/deno.json +++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/deno.json @@ -4,10 +4,11 @@ "strict": true }, "imports": { - "hono": "npm:hono@^4.6.14", + "hono": "npm:hono@4.10.6", "zod": "npm:zod@^4.1.12", - "ai": "npm:ai@^5.0.90", - "@ai-sdk/openai": "npm:@ai-sdk/openai@^2.0.32", - "@ai-sdk/mcp": "npm:@ai-sdk/mcp@latest" + "ai": "npm:ai@^5.0.93", + "@ai-sdk/openai": "npm:@ai-sdk/openai@^2.0.68", + "@ai-sdk/mcp": "npm:@ai-sdk/mcp@latest", + "@modelcontextprotocol/sdk/client/streamableHttp.js": "npm:@modelcontextprotocol/sdk@^1.22.0/client/streamableHttp.js" } } diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/index.ts b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/index.ts index ddacde6830b..6c73467c788 100644 --- a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/index.ts +++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/chat/index.ts @@ -1,9 +1,11 @@ +import { experimental_createMCPClient as createMCPClient } from '@ai-sdk/mcp' import { openai } from '@ai-sdk/openai' -import { streamText, type CoreMessage } from 'ai' +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' +import { convertToModelMessages, stepCountIs, streamText } from 'ai' import { corsHeaders } from '../_shared/cors.ts' type ChatRequestBody = { - messages?: CoreMessage[] + messages?: unknown[] model?: string } @@ -39,16 +41,77 @@ Deno.serve(async (req) => { const modelId = body.model && typeof body.model === 'string' ? body.model : 'gpt-4o' + let normalizedMessages + try { + normalizedMessages = convertToModelMessages(body.messages as any) + } catch (conversionError) { + console.error('Assistant chat message normalization error:', conversionError) + return new Response(JSON.stringify({ error: 'Invalid message format' }), { + status: 400, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }) + } + + // Create MCP client to connect to our MCP server + let mcpClient + let tools = {} + + try { + const supabaseUrl = Deno.env.get('SUPABASE_URL') + const mcpServerUrl = `${supabaseUrl}/functions/v1/mcp-server/mcp` + + // Use StreamableHTTPClientTransport for compatibility with mcp-lite's StreamableHttpTransport + const transport = new StreamableHTTPClientTransport(new URL(mcpServerUrl), { + requestInit: { + headers: { + Authorization: authHeader, + }, + }, + }) + + mcpClient = await createMCPClient({ + transport, + }) + + // Get available tools from the MCP server + tools = await mcpClient.tools() + } catch (mcpError) { + console.error('MCP client initialization error:', mcpError) + // Continue without MCP tools if initialization fails + } + try { const result = await streamText({ model: openai(modelId), - messages: body.messages, + messages: normalizedMessages, + stopWhen: stepCountIs(5), system: 'You are a helpful AI assistant.', + tools, + onFinish: async () => { + // Close the MCP client when the stream is complete + if (mcpClient) { + await mcpClient.close() + } + }, }) - return result.toTextStreamResponse({ headers: corsHeaders }) + return result.toUIMessageStreamResponse({ + headers: corsHeaders, + sendReasoning: true, + sendSources: true, + }) } catch (error) { console.error('Assistant chat error:', error) + + // Ensure MCP client is closed even on error + if (mcpClient) { + try { + await mcpClient.close() + } catch (closeError) { + console.error('Error closing MCP client:', closeError) + } + } + return new Response( JSON.stringify({ error: 'Failed to process chat request', diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/deno.json b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/deno.json index 78f0e38e9e7..33b00af98f8 100644 --- a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/deno.json +++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/deno.json @@ -4,9 +4,10 @@ "strict": true }, "imports": { - "hono": "npm:hono@^4.6.14", - "mcp-lite": "npm:mcp-lite@0.8.2", + "hono": "npm:hono@4.10.6", + "mcp-lite": "npm:mcp-lite@0.10.0", "zod": "npm:zod@^4.1.12", "@supabase/supabase-js": "npm:@supabase/supabase-js@^2" - } + }, + "verify_jwt": false } diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/index.ts b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/index.ts index 3d397388875..07e0207df47 100644 --- a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/index.ts +++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/index.ts @@ -1,13 +1,8 @@ -// Follow this setup guide to integrate the Deno language server with your editor: -// https://deno.land/manual/getting_started/setup_your_environment -// This enables autocomplete, go to definition, etc. -// -// Setup type definitions for built-in Supabase Runtime APIs -/// - -import { Hono } from "hono"; -import { McpServer, StreamableHttpTransport } from "mcp-lite"; -import { z } from "zod"; +import { Hono } from 'hono' +import { McpServer, StreamableHttpTransport } from 'mcp-lite' +import { z } from 'zod' +import { corsHeaders } from '../_shared/cors.ts' +import { createClient } from 'jsr:@supabase/supabase-js@2' // We create two Hono instances: // 1. `app` is the root handler for the Supabase Edge Function (must match the function name, e.g. /mcp-server) @@ -15,77 +10,286 @@ import { z } from "zod"; // This pattern is required because Supabase Edge Functions route all requests to //* const mcp = new McpServer({ - name: "supabase-assistant-mcp-server", - version: "1.0.0", + name: 'supabase-assistant-mcp-server', + version: '1.0.0', schemaAdapter: (schema: unknown) => z.toJSONSchema(schema as z.ZodType), -}); +}) -// Temporary placeholder tool while wiring up the rest of the MCP server. -mcp.tool("queryDatabase", { - description: - "Example placeholder tool that just echoes a string back to the caller.", - inputSchema: z.object({ - message: z - .string() - .default("Hello from the Supabase MCP server!") - .describe("Optional message you would like the tool to return"), - }), - handler: async ({ message }: { message: string }) => { - return { - content: [ - { - type: "text", - text: `Example tool response: ${message}`, +// Auth Header Management +// StreamableHttpTransport doesn't expose HTTP request headers to the MCP context (ctx.request.headers is undefined). +// We use a module-level variable to pass the auth header from the Hono handler to MCP middleware. +// This is safe because Supabase Edge Functions process requests sequentially, not concurrently. +// The header is set before processing and cleared in a finally block to prevent leaks. +// Alternative: Use InMemorySessionAdapter with custom session context, but adds unnecessary complexity. +let currentAuthHeader: string | null = null + +// Helper function to create authenticated Supabase client +function createAuthenticatedClient(authHeader: string) { + return createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_ANON_KEY')!, + { + global: { + headers: { + Authorization: authHeader, }, - ], - }; + }, + } + ) +} + +// Middleware to inject auth header into MCP context state +mcp.use(async (ctx: any, next: any) => { + // Try to get auth header from ctx.request.headers (doesn't work with StreamableHttpTransport) + // Fall back to the stored currentAuthHeader set by the Hono handler + const authHeader = ctx.request?.headers?.get?.('Authorization') || currentAuthHeader + + if (authHeader) { + // Make auth header available to all tool handlers via ctx.state + ctx.state.authHeader = authHeader + } + + await next() +}) + +mcp.tool('getUserTasks', { + description: 'Fetch tasks for the authenticated user with optional filtering by due date and title search.', + inputSchema: z.object({ + due_date: z + .string() + .optional() + .describe('Optional due date filter in YYYY-MM-DD format. Returns tasks with this exact due date.'), + title_search: z + .string() + .optional() + .describe('Optional title search term. Returns tasks where the title contains this text (case-insensitive).'), + }), + handler: async ({ due_date, title_search }: { due_date?: string; title_search?: string }, ctx: any) => { + const authHeader = ctx.state?.authHeader + + if (!authHeader) { + return { + content: [ + { + type: 'text', + text: 'Error: Authentication required to fetch tasks.', + }, + ], + isError: true, + } + } + + try { + const supabase = createAuthenticatedClient(authHeader) + + let query = supabase + .from('tasks') + .select('id, created_at, title, assignee_id, due_at') + .order('due_at', { ascending: true, nullsFirst: false }) + + // Apply due_date filter if provided + if (due_date) { + query = query.eq('due_at', due_date) + } + + // Apply title_search filter if provided + if (title_search) { + query = query.ilike('title', `%${title_search}%`) + } + + const { data, error } = await query + + if (error) { + return { + content: [ + { + type: 'text', + text: `Error fetching tasks: ${error.message}`, + }, + ], + isError: true, + } + } + + const taskCount = data?.length || 0 + const tasksText = data && taskCount > 0 + ? data.map((task) => { + const dueDate = task.due_at ? `Due: ${task.due_at}` : 'No due date' + return `- [${task.id}] ${task.title || 'Untitled'} (${dueDate})` + }).join('\n') + : 'No tasks found.' + + return { + content: [ + { + type: 'text', + text: `Found ${taskCount} task(s):\n\n${tasksText}`, + }, + ], + } + } catch (err) { + return { + content: [ + { + type: 'text', + text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`, + }, + ], + isError: true, + } + } }, -}); +}) -const transport = new StreamableHttpTransport(); -const httpHandler = transport.bind(mcp); +mcp.tool('createTask', { + description: 'Create a task for the authenticated user. Optionally provide a due date (YYYY-MM-DD).', + inputSchema: z.object({ + title: z + .string() + .min(1, 'Title must not be empty') + .describe('Task title to display in the list.'), + due_date: z + .string() + .optional() + .describe('Optional due date in YYYY-MM-DD format.'), + }), + handler: async ({ title, due_date }: { title: string; due_date?: string }, ctx: any) => { + const authHeader = ctx.state?.authHeader -const app = new Hono(); -const mcpApp = new Hono(); + if (!authHeader) { + return { + content: [ + { + type: 'text', + text: 'Error: Authentication required to create tasks.', + }, + ], + isError: true, + } + } -mcpApp.get("/", (c) => { + try { + const supabase = createAuthenticatedClient(authHeader) + + const { + data: { user }, + error: userError, + } = await supabase.auth.getUser() + + if (userError || !user) { + return { + content: [ + { + type: 'text', + text: `Error retrieving authenticated user: ${userError?.message ?? 'Not found'}`, + }, + ], + isError: true, + } + } + + const { data, error } = await supabase + .from('tasks') + .insert({ + title, + due_at: due_date ?? null, + assignee_id: user.id, + }) + .select('id, title, due_at, assignee_id') + .single() + + if (error || !data) { + return { + content: [ + { + type: 'text', + text: `Error creating task: ${error?.message ?? 'Unknown error'}`, + }, + ], + isError: true, + } + } + + const dueDateText = data.due_at ? `Due ${data.due_at}` : 'No due date set' + + return { + content: [ + { + type: 'text', + text: `Created task [${data.id}] "${data.title}" for user ${data.assignee_id}. ${dueDateText}.`, + }, + ], + } + } catch (err) { + return { + content: [ + { + type: 'text', + text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`, + }, + ], + isError: true, + } + } + }, +}) + +const transport = new StreamableHttpTransport() +const httpHandler = transport.bind(mcp) + +const app = new Hono() +const mcpApp = new Hono() + +// Handle OPTIONS requests for CORS +mcpApp.use('*', async (c, next) => { + if (c.req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }) + } + await next() +}) + +mcpApp.get('/', (c) => { return c.json({ - message: "MCP Server on Supabase Edge Functions", + message: 'MCP Server on Supabase Edge Functions', endpoints: { - mcp: "/mcp", - health: "/health", + mcp: '/mcp', + health: '/health', }, - }); -}); + }) +}) -mcpApp.get("/health", (c) => { +mcpApp.get('/health', (c) => { return c.json({ - message: "Service is up and running", - }); -}); + message: 'Service is up and running', + }) +}) -mcpApp.all("/mcp", async (c) => { - const response = await httpHandler(c.req.raw); - return response; -}); +// Handle both POST (for regular MCP requests) and GET (for SSE connections) +const handleMcpRequest = async (c: any) => { + // Validate Authorization header at the Hono level + // (StreamableHttpTransport doesn't expose HTTP headers to MCP context) + const authHeader = c.req.header('Authorization') + + if (!authHeader) { + return c.json({ error: 'Unauthorized' }, { status: 401, headers: corsHeaders }) + } + + // Store auth header so MCP middleware can inject it into ctx.state for tool handlers + currentAuthHeader = authHeader + + try { + const response = await httpHandler(c.req.raw) + return response + } finally { + // Clean up auth header after request completes to prevent leaks + currentAuthHeader = null + } +} + +mcpApp.post('/mcp', handleMcpRequest) +mcpApp.get('/mcp', handleMcpRequest) // Mount the MCP app at /mcp-server (matches the function name) -app.route("/mcp-server", mcpApp); +app.route('/mcp-server', mcpApp) -export default app; - -/* To invoke locally: - - 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) - 2. Make an HTTP request: - - curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/mcp-server/mcp' \ - --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \ - --header 'Content-Type: application/json' \ - --data '{"jsonrpc":"2.0","method":"tools/list","id":1}' - - 3. Test the health endpoint: - - curl 'http://127.0.0.1:54321/functions/v1/mcp-server/health' - -*/ +export default app diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/lib/supabase.ts b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/lib/supabase.ts new file mode 100644 index 00000000000..c76f10457a7 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/lib/supabase.ts @@ -0,0 +1,15 @@ +import { createClient } from 'jsr:@supabase/supabase-js@2' + +export function createAuthenticatedClient(authHeader: string) { + return createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_ANON_KEY')!, + { + global: { + headers: { + Authorization: authHeader, + }, + }, + } + ) +} diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/create-task.ts b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/create-task.ts new file mode 100644 index 00000000000..7908d4500ac --- /dev/null +++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/create-task.ts @@ -0,0 +1,98 @@ +import { z } from 'zod' +import { createAuthenticatedClient } from '../lib/supabase.ts' + +export const createTaskTool = { + name: 'createTask', + options: { + description: 'Create a task for the authenticated user. Optionally provide a due date (YYYY-MM-DD).', + inputSchema: z.object({ + title: z + .string() + .min(1, 'Title must not be empty') + .describe('Task title to display in the list.'), + due_date: z + .string() + .optional() + .describe('Optional due date in YYYY-MM-DD format.'), + }), + handler: async ({ title, due_date }: { title: string; due_date?: string }, ctx: any) => { + const authHeader = ctx.state?.authHeader + + if (!authHeader) { + return { + content: [ + { + type: 'text', + text: 'Error: Authentication required to create tasks.', + }, + ], + isError: true, + } + } + + try { + const supabase = createAuthenticatedClient(authHeader) + + const { + data: { user }, + error: userError, + } = await supabase.auth.getUser() + + if (userError || !user) { + return { + content: [ + { + type: 'text', + text: `Error retrieving authenticated user: ${userError?.message ?? 'Not found'}`, + }, + ], + isError: true, + } + } + + const { data, error } = await supabase + .from('tasks') + .insert({ + title, + due_at: due_date ?? null, + assignee_id: user.id, + }) + .select('id, title, due_at, assignee_id') + .single() + + if (error || !data) { + return { + content: [ + { + type: 'text', + text: `Error creating task: ${error?.message ?? 'Unknown error'}`, + }, + ], + isError: true, + } + } + + const dueDateText = data.due_at ? `Due ${data.due_at}` : 'No due date set' + + return { + content: [ + { + type: 'text', + text: `Created task [${data.id}] "${data.title}" for user ${data.assignee_id}. ${dueDateText}.`, + }, + ], + } + } catch (err) { + return { + content: [ + { + type: 'text', + text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`, + }, + ], + isError: true, + } + } + }, + }, +} diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/get-user-tasks.ts b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/get-user-tasks.ts new file mode 100644 index 00000000000..f74ca3fd89a --- /dev/null +++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/get-user-tasks.ts @@ -0,0 +1,101 @@ +import { z } from 'zod' +import { createAuthenticatedClient } from '../lib/supabase.ts' + +export const getUserTasksTool = { + name: 'getUserTasks', + options: { + description: + 'Fetch tasks for the authenticated user with optional filtering by due date and title search.', + inputSchema: z.object({ + due_date: z + .string() + .optional() + .describe('Optional due date filter in YYYY-MM-DD format. Returns tasks with this exact due date.'), + title_search: z + .string() + .optional() + .describe('Optional title search term. Returns tasks where the title contains this text (case-insensitive).'), + }), + handler: async ( + { due_date, title_search }: { due_date?: string; title_search?: string }, + ctx: any + ) => { + const authHeader = ctx.state?.authHeader + + if (!authHeader) { + return { + content: [ + { + type: 'text', + text: 'Error: Authentication required to fetch tasks.', + }, + ], + isError: true, + } + } + + try { + const supabase = createAuthenticatedClient(authHeader) + + let query = supabase + .from('tasks') + .select('id, created_at, title, assignee_id, due_at') + .order('due_at', { ascending: true, nullsFirst: false }) + + if (due_date) { + query = query.eq('due_at', due_date) + } + + if (title_search) { + query = query.ilike('title', `%${title_search}%`) + } + + const { data, error } = await query + + if (error) { + return { + content: [ + { + type: 'text', + text: `Error fetching tasks: ${error.message}`, + }, + ], + isError: true, + } + } + + const taskCount = data?.length || 0 + const tasksText = + data && taskCount > 0 + ? data + .map((task) => { + const dueDate = task.due_at ? `Due: ${task.due_at}` : 'No due date' + return `- [${task.id}] ${task.title || 'Untitled'} (${dueDate})` + }) + .join('\n') + : 'No tasks found.' + + return { + content: [ + { + type: 'text', + text: `Found ${taskCount} task(s):\n\n${tasksText}`, + }, + ], + } + } catch (err) { + return { + content: [ + { + type: 'text', + text: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`, + }, + ], + isError: true, + } + } + }, + }, +} + +export type ToolDefinition = typeof getUserTasksTool diff --git a/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/registry.ts b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/registry.ts new file mode 100644 index 00000000000..7239bd72f1f --- /dev/null +++ b/apps/ui-library/registry/default/blocks/assistant/supabase/functions/mcp-server/tools/registry.ts @@ -0,0 +1,6 @@ +import { createTaskTool } from './create-task.ts' +import { getUserTasksTool } from './get-user-tasks.ts' + +export const toolRegistry = [getUserTasksTool, createTaskTool] + +export type ToolDefinition = (typeof toolRegistry)[number] diff --git a/apps/ui-library/registry/default/components/ui/popover.tsx b/apps/ui-library/registry/default/components/ui/popover.tsx index cb3d6cd16c7..69739ae8a31 100644 --- a/apps/ui-library/registry/default/components/ui/popover.tsx +++ b/apps/ui-library/registry/default/components/ui/popover.tsx @@ -39,4 +39,8 @@ function PopoverAnchor({ ...props }: React.ComponentProps } -export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } +function PopoverClose({ ...props }: React.ComponentProps) { + return +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverClose } diff --git a/apps/ui-library/registry/default/examples/assistant-demo.tsx b/apps/ui-library/registry/default/examples/assistant-demo.tsx index 8d84f907a5f..3d4805492f2 100644 --- a/apps/ui-library/registry/default/examples/assistant-demo.tsx +++ b/apps/ui-library/registry/default/examples/assistant-demo.tsx @@ -1,97 +1,45 @@ 'use client' import { AssistantWidget } from '@/registry/default/blocks/assistant/components/assistant-widget' -import { Badge } from '@/registry/default/components/ui/badge' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/default/components/ui/card' -import { Separator } from '@/registry/default/components/ui/separator' -import { Check, Database, ShieldCheck, Sparkles } from 'lucide-react' - -const features = [ - { - title: 'Query with plain English', - description: 'Natural language requests turn into SQL via MCP tools.', - icon: Sparkles, - }, - { - title: 'Database aware', - description: 'Assistant can run validated SQL against your Supabase project.', - icon: Database, - }, - { - title: 'Secure by default', - description: 'Supabase auth token is forwarded for RLS protected data.', - icon: ShieldCheck, - }, -] - -const setupSteps = [ - 'Serve the `chat` and `mcp-server` Edge Functions.', - 'Expose `NEXT_PUBLIC_ASSISTANT_CHAT_URL` and `MCP_SERVER_URL`.', - 'Mount `` near the root layout of your app.', -] - -const Feature = ({ title, description, icon: Icon }: (typeof features)[number]) => ( -
-
- -
-
-

{title}

-

{description}

-
-
-) - -const SetupChecklist = () => ( -
-

- Quick setup -

-
    - {setupSteps.map((step) => ( -
  • - - {step} -
  • - ))} -
-
-) +import { createClient } from '@/registry/default/blocks/assistant/lib/supabase/client' +import { useEffect } from 'react' const AssistantDemoContent = () => ( - - - - AI Assistant - - Ask your data anything - - The Assistant widget pairs MCP tooling with Supabase authentication so you can run secure, - natural-language queries against your database without leaving the page. - - - -
- {features.map((feature) => ( - - ))} -
- - -

- Tip: Click the bubble in the bottom-right corner to open the live assistant powered by the - same block you install via the registry. -

-
-
-) - -const AssistantDemo = () => ( -
-
- - +
+

+ Click the Assistant widget and ask it to create, read, update, or delete tasks in your + database. Tasks will be scoped to your current session. +

) +const AssistantDemo = () => { + useEffect(() => { + const ensureSession = async () => { + const supabase = createClient() + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) { + const { error } = await supabase.auth.signInAnonymously() + if (error) { + console.error('Anonymous sign-in failed', error) + } + } + } + + ensureSession().catch((error) => { + console.error('Failed to check session', error) + }) + }, []) + + return ( +
+ + +
+ ) +} + export default AssistantDemo diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0639e85b89..9e03cb8f943 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -400,7 +400,7 @@ importers: version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/nextjs': specifier: ^10.3.0 - version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) + version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) '@supabase/supabase-js': specifier: 'catalog:' version: 2.78.0 @@ -815,7 +815,7 @@ importers: version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/nextjs': specifier: ^10.3.0 - version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) + version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) '@std/path': specifier: npm:@jsr/std__path@^1.0.8 version: '@jsr/std__path@1.0.8' @@ -1250,8 +1250,8 @@ importers: apps/ui-library: dependencies: '@ai-sdk/react': - specifier: 2.0.45 - version: 2.0.45(react@18.3.1)(zod@3.25.76) + specifier: ^2.0.68 + version: 2.0.93(react@18.3.1)(zod@3.25.76) '@hookform/resolvers': specifier: ^3.1.1 version: 3.3.1(react-hook-form@7.47.0(react@18.3.1)) @@ -1361,8 +1361,8 @@ importers: specifier: ^12.9.2 version: 12.9.2(@types/react@18.3.3)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ai: - specifier: ^5.0.90 - version: 5.0.90(zod@3.25.76) + specifier: ^5.0.93 + version: 5.0.93(zod@3.25.76) axios: specifier: ^1.12.0 version: 1.12.2 @@ -1504,7 +1504,7 @@ importers: version: 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-start': specifier: ^1.114.25 - version: 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1) + version: 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1) '@types/common-tags': specifier: ^1.8.4 version: 1.8.4 @@ -1606,7 +1606,7 @@ importers: version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/nextjs': specifier: ^10 - version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) + version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0) '@supabase/supabase-js': specifier: 'catalog:' version: 2.78.0 @@ -1693,7 +1693,7 @@ importers: version: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@8.1.1) next-seo: specifier: ^6.5.0 - version: 6.5.0(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 6.5.0(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2644,8 +2644,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 - '@ai-sdk/gateway@2.0.7': - resolution: {integrity: sha512-/AI5AKi4vOK9SEb8Z1dfXkhsJ5NAfWsoJQc96B/mzn2KIrjw5occOjIwD06scuhV9xWlghCoXJT1sQD9QH/tyg==} + '@ai-sdk/gateway@2.0.9': + resolution: {integrity: sha512-E6x4h5CPPPJ0za1r5HsLtHbeI+Tp3H+YFtcH8G3dSSPFE6w+PZINzB4NxLZmg1QqSeA5HTP3ZEzzsohp0o2GEw==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -2668,8 +2668,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 - '@ai-sdk/provider-utils@3.0.16': - resolution: {integrity: sha512-lsWQY9aDXHitw7C1QRYIbVGmgwyT98TF3MfM8alNIXKpdJdi+W782Rzd9f1RyOfgRmZ08gJ2EYNDhWNK7RqpEA==} + '@ai-sdk/provider-utils@3.0.17': + resolution: {integrity: sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -2694,6 +2694,16 @@ packages: zod: optional: true + '@ai-sdk/react@2.0.93': + resolution: {integrity: sha512-2TzhpQr10HuWxpqyHpSAUMRUqD1G2O73J2sAaJChomVDbjr7BwpM0mdR3aRamCXNtuLiJmTFQhbNzw8fXMBdYw==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.25.76 || ^4.1.8 + peerDependenciesMeta: + zod: + optional: true + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -10105,8 +10115,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 - ai@5.0.90: - resolution: {integrity: sha512-bawNN10N2cXzFedbDdNUZo8KkcGp12VX1b+mCL5dfllh6WmLsIYYME7GVxsRJvHvPP7xRhuds5fn0jtLyxGnZw==} + ai@5.0.93: + resolution: {integrity: sha512-9eGcu+1PJgPg4pRNV4L7tLjRR3wdJC9CXQoNMvtqvYNOLZHFCzjHtVIOr2SIkoJJeu2+sOy3hyiSuTmy2MA40g==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -19919,10 +19929,10 @@ snapshots: '@ai-sdk/provider-utils': 3.0.9(zod@3.25.76) zod: 3.25.76 - '@ai-sdk/gateway@2.0.7(zod@3.25.76)': + '@ai-sdk/gateway@2.0.9(zod@3.25.76)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.16(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.17(zod@3.25.76) '@vercel/oidc': 3.0.3 zod: 3.25.76 @@ -19946,7 +19956,7 @@ snapshots: zod: 3.25.76 zod-to-json-schema: 3.24.5(zod@3.25.76) - '@ai-sdk/provider-utils@3.0.16(zod@3.25.76)': + '@ai-sdk/provider-utils@3.0.17(zod@3.25.76)': dependencies: '@ai-sdk/provider': 2.0.0 '@standard-schema/spec': 1.0.0 @@ -19974,6 +19984,16 @@ snapshots: optionalDependencies: zod: 3.25.76 + '@ai-sdk/react@2.0.93(react@18.3.1)(zod@3.25.76)': + dependencies: + '@ai-sdk/provider-utils': 3.0.17(zod@3.25.76) + ai: 5.0.93(zod@3.25.76) + react: 18.3.1 + swr: 2.2.5(react@18.3.1) + throttleit: 2.1.0 + optionalDependencies: + zod: 3.25.76 + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -27518,7 +27538,7 @@ snapshots: '@sentry/core@10.3.0': {} - '@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': + '@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.36.0 @@ -28587,7 +28607,7 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-start-client@1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)': + '@tanstack/react-start-client@1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)': dependencies: '@tanstack/react-router': 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/router-core': 1.114.25 @@ -28598,7 +28618,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) + vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -28642,7 +28662,7 @@ snapshots: - xml2js - yaml - '@tanstack/react-start-config@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)': + '@tanstack/react-start-config@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)': dependencies: '@tanstack/react-start-plugin': 1.114.12(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1) '@tanstack/router-core': 1.114.25 @@ -28652,11 +28672,11 @@ snapshots: '@tanstack/start-server-functions-handler': 1.114.25 '@vitejs/plugin-react': 4.3.4(supports-color@8.1.1)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)) import-meta-resolve: 4.1.0 - nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(supports-color@8.1.1)(typescript@5.9.2) + nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.9.2) ofetch: 1.4.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) + vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) vite: 7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1) zod: 3.25.76 transitivePeerDependencies: @@ -28734,11 +28754,11 @@ snapshots: - tsx - yaml - '@tanstack/react-start-router-manifest@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)': + '@tanstack/react-start-router-manifest@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)': dependencies: '@tanstack/router-core': 1.114.25 tiny-invariant: 1.3.3 - vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) + vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -28797,13 +28817,13 @@ snapshots: tiny-warning: 1.0.3 unctx: 2.4.1 - '@tanstack/react-start@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)': + '@tanstack/react-start@1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1)': dependencies: - '@tanstack/react-start-client': 1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) - '@tanstack/react-start-config': 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1) - '@tanstack/react-start-router-manifest': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) + '@tanstack/react-start-client': 1.114.27(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) + '@tanstack/react-start-config': 1.114.27(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/node@22.13.14)(aws4fetch@1.0.20)(babel-plugin-macros@3.1.0)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@7.1.11(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(webpack@5.94.0(esbuild@0.25.2))(yaml@2.8.1) + '@tanstack/react-start-router-manifest': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) '@tanstack/react-start-server': 1.114.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/start-api-routes': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) + '@tanstack/start-api-routes': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) '@tanstack/start-server-functions-client': 1.114.25(@types/node@22.13.14)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1) '@tanstack/start-server-functions-handler': 1.114.25 '@tanstack/start-server-functions-server': 1.114.12(@types/node@22.13.14)(babel-plugin-macros@3.1.0)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1) @@ -28954,11 +28974,11 @@ snapshots: - tsx - yaml - '@tanstack/start-api-routes@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)': + '@tanstack/start-api-routes@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1)': dependencies: '@tanstack/router-core': 1.114.25 '@tanstack/start-server-core': 1.114.25 - vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) + vinxi: 0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -29866,7 +29886,7 @@ snapshots: optionalDependencies: '@aws-sdk/credential-provider-web-identity': 3.830.0 - '@vercel/nft@0.29.2(rollup@4.50.2)(supports-color@8.1.1)': + '@vercel/nft@0.29.2(encoding@0.1.13)(rollup@4.50.2)(supports-color@8.1.1)': dependencies: '@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13)(supports-color@8.1.1) '@rollup/pluginutils': 5.1.4(rollup@4.50.2) @@ -30401,11 +30421,11 @@ snapshots: '@opentelemetry/api': 1.9.0 zod: 3.25.76 - ai@5.0.90(zod@3.25.76): + ai@5.0.93(zod@3.25.76): dependencies: - '@ai-sdk/gateway': 2.0.7(zod@3.25.76) + '@ai-sdk/gateway': 2.0.9(zod@3.25.76) '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.16(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.17(zod@3.25.76) '@opentelemetry/api': 1.9.0 zod: 3.25.76 @@ -36709,7 +36729,7 @@ snapshots: next: 15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 - next-seo@6.5.0(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-seo@6.5.0(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: next: 15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) react: 18.3.1 @@ -36750,7 +36770,7 @@ snapshots: nice-try@1.0.5: {} - nitropack@2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(supports-color@8.1.1)(typescript@5.9.2): + nitropack@2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.9.2): dependencies: '@cloudflare/kv-asset-handler': 0.4.0 '@netlify/functions': 3.0.4 @@ -36761,7 +36781,7 @@ snapshots: '@rollup/plugin-node-resolve': 16.0.1(rollup@4.50.2) '@rollup/plugin-replace': 6.0.2(rollup@4.50.2) '@rollup/plugin-terser': 0.4.4(rollup@4.50.2) - '@vercel/nft': 0.29.2(rollup@4.50.2)(supports-color@8.1.1) + '@vercel/nft': 0.29.2(encoding@0.1.13)(rollup@4.50.2)(supports-color@8.1.1) archiver: 7.0.1 c12: 3.0.2(magicast@0.3.5) chokidar: 4.0.3 @@ -41708,7 +41728,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vinxi@0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1): + vinxi@0.5.3(@electric-sql/pglite@0.2.15)(@types/node@22.13.14)(aws4fetch@1.0.20)(db0@0.3.1(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(yaml@2.8.1): dependencies: '@babel/core': 7.28.4(supports-color@8.1.1) '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4(supports-color@8.1.1)) @@ -41730,7 +41750,7 @@ snapshots: hookable: 5.5.3 http-proxy: 1.18.1 micromatch: 4.0.8 - nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(supports-color@8.1.1)(typescript@5.9.2) + nitropack: 2.11.7(@electric-sql/pglite@0.2.15)(aws4fetch@1.0.20)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.9.2) node-fetch-native: 1.6.6 path-to-regexp: 6.3.0 pathe: 1.1.2