diff --git a/app/actions.tsx b/app/actions.tsx index 64354705..f3b3c44f 100644 --- a/app/actions.tsx +++ b/app/actions.tsx @@ -12,7 +12,8 @@ import { Spinner } from '@/components/ui/spinner'; import { Section } from '@/components/section'; import { FollowupPanel } from '@/components/followup-panel'; import { inquire, researcher, taskManager, querySuggestor } from '@/lib/agents'; -import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial'; // Added import for the hook +// Removed import of useGeospatialToolMcp as it no longer exists and was incorrectly used here. +// The geospatialTool (if used by agents like researcher) now manages its own MCP client. import { writer } from '@/lib/agents/writer'; import { saveChat, getSystemPrompt } from '@/lib/actions/chat'; // Added getSystemPrompt import { Chat, AIMessage } from '@/lib/types'; @@ -30,7 +31,8 @@ type RelatedQueries = { items: { query: string }[]; }; -async function submit(formData?: FormData, skip?: boolean, mcp?: any) { // Added mcp as a parameter +// Removed mcp parameter from submit, as geospatialTool now handles its client. +async function submit(formData?: FormData, skip?: boolean) { 'use server'; // TODO: Update agent function signatures in lib/agents/researcher.tsx and lib/agents/writer.tsx @@ -141,12 +143,13 @@ async function submit(formData?: FormData, skip?: boolean, mcp?: any) { // Added : answer.length === 0 && !errorOccurred ) { // Search the web and generate the answer + // Removed mcp argument from researcher call const { fullResponse, hasError, toolResponses } = await researcher( currentSystemPrompt, uiStream, streamText, messages, - mcp, // Pass mcp to researcher + // mcp, // mcp instance is no longer passed down useSpecificAPI ); answer = fullResponse; diff --git a/bun.lockb b/bun.lockb index 1ab2228b..2aff2ca1 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index f29a8a6f..6d58759d 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -4,7 +4,7 @@ import { useEffect, useState, useRef } from 'react' import { useRouter } from 'next/navigation' import type { AI, UIState } from '@/app/actions' import { useUIState, useActions } from 'ai/rsc' -import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial' // Added import +// Removed import of useGeospatialToolMcp as it's no longer used/available import { cn } from '@/lib/utils' import { UserMessage } from './user-message' import { Button } from './ui/button' @@ -21,7 +21,7 @@ interface ChatPanelProps { export function ChatPanel({ messages, input, setInput }: ChatPanelProps) { const [, setMessages] = useUIState() const { submit } = useActions() - const mcp = useGeospatialToolMcp() // Call the hook + // Removed mcp instance as it's no longer passed to submit const [isButtonPressed, setIsButtonPressed] = useState(false) const [isMobile, setIsMobile] = useState(false) const inputRef = useRef(null) @@ -58,7 +58,8 @@ export function ChatPanel({ messages, input, setInput }: ChatPanelProps) { } ]) const formData = new FormData(e.currentTarget) - const responseMessage = await submit(formData, undefined, mcp) // Pass mcp + // Removed mcp argument from submit call + const responseMessage = await submit(formData) setMessages(currentMessages => [...currentMessages, responseMessage as any]) } diff --git a/components/copilot.tsx b/components/copilot.tsx index f4ddd0a5..af110a89 100644 --- a/components/copilot.tsx +++ b/components/copilot.tsx @@ -8,7 +8,7 @@ import { Button } from './ui/button' import { Card } from './ui/card' import { ArrowRight, Check, FastForward, Sparkles } from 'lucide-react' import { useActions, useStreamableValue, useUIState } from 'ai/rsc' -import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial' // Added import +// Removed import of useGeospatialToolMcp as it's no longer used/available import type { AI } from '@/app/actions' import { @@ -32,7 +32,7 @@ export const Copilot: React.FC = ({ inquiry }: CopilotProps) => { const [isButtonDisabled, setIsButtonDisabled] = useState(true) const [, setMessages] = useUIState() const { submit } = useActions() - const mcp = useGeospatialToolMcp() // Call the hook + // Removed mcp instance as it's no longer passed to submit const handleInputChange = (event: React.ChangeEvent) => { setQuery(event.target.value) @@ -79,7 +79,8 @@ export const Copilot: React.FC = ({ inquiry }: CopilotProps) => { ? undefined : new FormData(e.target as HTMLFormElement) - const response = await submit(formData, skip, mcp) // Pass mcp + // Removed mcp argument from submit call + const response = await submit(formData, skip) setMessages(currentMessages => [...currentMessages, response]) } diff --git a/components/followup-panel.tsx b/components/followup-panel.tsx index 9297e9fb..336e9a42 100644 --- a/components/followup-panel.tsx +++ b/components/followup-panel.tsx @@ -4,7 +4,7 @@ import { useState } from 'react' import { Button } from './ui/button' import { Input } from './ui/input' import { useActions, useUIState } from 'ai/rsc' -import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial' // Added import +// Removed import of useGeospatialToolMcp as it's no longer used/available import type { AI } from '@/app/actions' import { UserMessage } from './user-message' import { ArrowRight } from 'lucide-react' @@ -12,7 +12,7 @@ import { ArrowRight } from 'lucide-react' export function FollowupPanel() { const [input, setInput] = useState('') const { submit } = useActions() - const mcp = useGeospatialToolMcp() // Call the hook + // Removed mcp instance as it's no longer passed to submit const [, setMessages] = useUIState() const handleSubmit = async (event: React.FormEvent) => { @@ -25,7 +25,8 @@ export function FollowupPanel() { component: } - const responseMessage = await submit(formData, undefined, mcp) // Pass mcp + // Removed mcp argument from submit call + const responseMessage = await submit(formData) setMessages(currentMessages => [ ...currentMessages, userMessage, diff --git a/components/search-related.tsx b/components/search-related.tsx index 92a2ee68..b65a7be3 100644 --- a/components/search-related.tsx +++ b/components/search-related.tsx @@ -11,7 +11,7 @@ import { StreamableValue } from 'ai/rsc' import { AI } from '@/app/actions' -import { useGeospatialToolMcp } from '@/lib/agents/tools/geospatial' // Added import +// Removed import of useGeospatialToolMcp as it's no longer used/available import { UserMessage } from './user-message' import { PartialRelated } from '@/lib/schema/related' @@ -23,7 +23,7 @@ export const SearchRelated: React.FC = ({ relatedQueries }) => { const { submit } = useActions() - const mcp = useGeospatialToolMcp() // Call the hook + // Removed mcp instance as it's no longer passed to submit const [, setMessages] = useUIState() const [data, error, pending] = useStreamableValue(relatedQueries) @@ -46,7 +46,8 @@ export const SearchRelated: React.FC = ({ component: } - const responseMessage = await submit(formData, undefined, mcp) // Pass mcp + // Removed mcp argument from submit call + const responseMessage = await submit(formData) setMessages(currentMessages => [ ...currentMessages, userMessage, diff --git a/lib/agents/researcher.tsx b/lib/agents/researcher.tsx index 28dfc11f..6ef6d53b 100644 --- a/lib/agents/researcher.tsx +++ b/lib/agents/researcher.tsx @@ -16,7 +16,7 @@ export async function researcher( uiStream: ReturnType, streamText: ReturnType>, messages: CoreMessage[], - mcp: any, // Moved mcp before optional parameter + // mcp: any, // Removed mcp parameter useSpecificModel?: boolean ) { let fullResponse = '' @@ -56,7 +56,7 @@ Match the language of your response to the user's language.`; tools: getTools({ uiStream, fullResponse, - mcp // Pass the mcp parameter to getTools + // mcp // mcp parameter is no longer passed to getTools }) }) diff --git a/lib/agents/tools/geospatial.tsx b/lib/agents/tools/geospatial.tsx index 967d54dd..012e8f4e 100644 --- a/lib/agents/tools/geospatial.tsx +++ b/lib/agents/tools/geospatial.tsx @@ -1,187 +1,301 @@ -import dotenv from 'dotenv'; -dotenv.config(); - -import { useMcp } from 'use-mcp/react'; +/** + * Fixed geospatial tool with improved error handling and schema + */ import { createStreamableUI, createStreamableValue } from 'ai/rsc'; - import { BotMessage } from '@/components/message'; import { geospatialQuerySchema } from '@/lib/schema/geospatial'; +import { Client as MCPClientClass } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import { createSmitheryUrl } from '@smithery/sdk'; + +export type McpClient = MCPClientClass; -// Define proper MCP type -export type McpClient = ReturnType; - -export function useGeospatialToolMcp() { - const mcpServerUrl = - 'https://server.smithery.ai/mapbox-mcp-server/mcp?api_key=705b0222-a657-4cd2-b180-80c406cf6179&profile=smooth-lemur-vfUbUE'; - const mcp = useMcp({ - url: mcpServerUrl, - debug: process.env.NODE_ENV === 'development', - autoReconnect: true, - autoRetry: 5000, +async function getConnectedMcpClient(): Promise { + // More detailed environment variable validation + const apiKey = process.env.NEXT_PUBLIC_SMITHERY_API_KEY; + const mapboxAccessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN; + const profileId = process.env.NEXT_PUBLIC_SMITHERY_PROFILE_ID; + + console.log('[GeospatialTool] Environment check:', { + apiKey: apiKey ? `${apiKey.substring(0, 8)}...` : 'MISSING', + mapboxAccessToken: mapboxAccessToken ? `${mapboxAccessToken.substring(0, 8)}...` : 'MISSING', + profileId: profileId ? `${profileId.substring(0, 8)}...` : 'MISSING', }); - return mcp; + if (!apiKey || !mapboxAccessToken || !profileId) { + console.error('[GeospatialTool] Missing required environment variables'); + return null; + } + + // Validate environment variables format + if (!apiKey.trim() || !mapboxAccessToken.trim() || !profileId.trim()) { + console.error('[GeospatialTool] Empty environment variables detected'); + return null; + } + + let config; + try { + // Try to load the config file with better error handling + const mapboxMcpConfig = await import('QCX/mapbox_mcp_config.json'); + + config = { + ...mapboxMcpConfig.default || mapboxMcpConfig, + mapboxAccessToken + }; + console.log('[GeospatialTool] Config loaded successfully'); + } catch (configError: any) { + console.error('[GeospatialTool] Failed to load mapbox config:', configError.message); + // Fallback to a basic config + config = { + mapboxAccessToken, + version: '1.0.0', + name: 'mapbox-mcp-server' + }; + console.log('[GeospatialTool] Using fallback config'); + } + + const mcpServerBaseUrl = 'https://server.smithery.ai/mapbox-mcp-server/mcp'; + const smitheryUrlOptions = { config, apiKey, profileId }; + + let serverUrlToUse; + try { + serverUrlToUse = createSmitheryUrl(mcpServerBaseUrl, smitheryUrlOptions); + const urlDisplay = serverUrlToUse.toString().split('?')[0]; + console.log('[GeospatialTool] MCP Server URL created:', urlDisplay); + + // Validate the URL + if (!serverUrlToUse.href || !serverUrlToUse.href.startsWith('https://')) { + throw new Error('Invalid server URL generated'); + } + } catch (urlError: any) { + console.error('[GeospatialTool] Error creating Smithery URL:', urlError.message); + console.error('[GeospatialTool] URL options:', { + baseUrl: mcpServerBaseUrl, + hasConfig: !!config, + hasApiKey: !!apiKey, + hasProfileId: !!profileId + }); + return null; + } + + let transport; + let client; + + try { + transport = new StreamableHTTPClientTransport(serverUrlToUse); + console.log('[GeospatialTool] Transport created successfully'); + } catch (transportError: any) { + console.error('[GeospatialTool] Failed to create transport:', transportError.message); + return null; + } + + try { + client = new MCPClientClass({ + name: 'GeospatialToolClient', + version: '1.0.0' + }); + console.log('[GeospatialTool] MCP Client instance created'); + } catch (clientError: any) { + console.error('[GeospatialTool] Failed to create MCP client:', clientError.message); + return null; + } + + try { + console.log('[GeospatialTool] Attempting to connect to MCP server...'); + + await Promise.race([ + client.connect(transport), + new Promise((_, reject) => { + setTimeout(() => reject(new Error('Connection timeout after 15 seconds')), 15000); + }), + ]); + + console.log('[GeospatialTool] Successfully connected to MCP server'); + + // Test the connection by listing available tools + try { + const tools = await client.listTools(); + console.log('[GeospatialTool] Available tools:', tools.tools?.map(t => t.name) || []); + } catch (listError: any) { + console.warn('[GeospatialTool] Could not list tools:', listError.message); + } + + return client; + } catch (connectionError: any) { + console.error('[GeospatialTool] MCP connection failed:', connectionError.message); + console.error('[GeospatialTool] Connection error details:', { + name: connectionError.name, + stack: connectionError.stack?.split('\n')[0], + serverUrl: serverUrlToUse?.toString().split('?')[0], + }); + + await closeClient(client); + return null; + } +} + +async function closeClient(client: MCPClientClass | null) { + if (!client) return; + + try { + await Promise.race([ + client.close(), + new Promise((_, reject) => { + setTimeout(() => reject(new Error('Close timeout after 5 seconds')), 5000); + }), + ]); + console.log('[GeospatialTool] MCP client closed successfully'); + } catch (error: any) { + console.error('[GeospatialTool] Error closing MCP client:', error.message); + } } export const geospatialTool = ({ uiStream, - mcp, }: { uiStream: ReturnType; - mcp: McpClient | null; }) => ({ - description: `Use this tool for any queries that involve locations, places, addresses, distances between places, directions, or finding points of interest on a map. This includes questions like: -- 'Where is [place name/address]?' -- 'Show me [place name/address] on the map.' -- 'What's the latitude and longitude of [place name]?' -- 'How far is it from [location A] to [location B]?' -- 'Give me directions from [location A] to [location B].' -- 'Find cafes near [current location/specified location].' -- 'What's around the [specific landmark]?' -- Any query that implies needing a map or spatial understanding.`, + description: `Use this tool for location-based queries including: +- Finding specific places, addresses, or landmarks +- Getting coordinates for locations +- Distance calculations between places +- Direction queries +- Map-related requests +- Geographic information lookup`, parameters: geospatialQuerySchema, - execute: async ({ query }: { query: string }) => { - // Provide immediate UI feedback + execute: async ({ query, queryType, includeMap }: { + query: string; + queryType?: string; + includeMap?: boolean; + }) => { + console.log('[GeospatialTool] Execute called with:', { query, queryType, includeMap }); + const uiFeedbackStream = createStreamableValue(); - uiFeedbackStream.done(`Looking up map information for: "${query}"...`); + uiFeedbackStream.done(`Processing geospatial query: "${query}". Connecting to mapping service...`); uiStream.append(); - // Check if MCP client is available - if (!mcp) { - console.warn('MCP client is not available, cannot proceed with geospatial query'); - const errorStream = createStreamableValue(); - errorStream.done('Geospatial functionality is currently unavailable.'); - uiStream.append(); + const mcpClient = await getConnectedMcpClient(); + + if (!mcpClient) { + const errorMessage = 'Geospatial functionality is currently unavailable. Please check your configuration and try again.'; + uiFeedbackStream.done(errorMessage); + uiStream.update(); return { type: 'MAP_QUERY_TRIGGER', originalUserInput: query, timestamp: new Date().toISOString(), mcp_response: null, - error: 'MCP client not available', + error: 'MCP client initialization failed - check environment variables and network connectivity', }; } - // Log environment variables for debugging (with API key masked) - console.log( - `[GeospatialTool] SMITHERY_PROFILE_ID: "${process.env.SMITHERY_PROFILE_ID ?? 'undefined'}"` - ); - console.log( - `[GeospatialTool] SMITHERY_API_KEY: ${ - process.env.SMITHERY_API_KEY - ? `****${process.env.SMITHERY_API_KEY.slice(-4)} (masked)` - : 'undefined' - }` - ); - - let mcpData: - | { - location: { - latitude?: number; - longitude?: number; - place_name?: string; - address?: string; - }; - mapUrl?: string; - } - | null = null; + let mcpData: { + location: { + latitude?: number; + longitude?: number; + place_name?: string; + address?: string; + }; + mapUrl?: string; + } | null = null; + let toolError: string | null = null; try { - console.log(`Attempting to connect to MCP server...`); - - if (!mcp || mcp.state !== 'ready') { - console.warn(`MCP client not ready${mcp ? ` (state: ${mcp.state})` : ''}, cannot proceed with tool call.`); - const errorStream = createStreamableValue(); - errorStream.done(`MCP client not ready${mcp ? ` (state: ${mcp.state})` : ''}. Please try again.`); - uiStream.append(); - return { - type: 'MAP_QUERY_TRIGGER', - originalUserInput: query, - timestamp: new Date().toISOString(), - mcp_response: null, - error: `MCP client not ready${mcp ? ` (state: ${mcp.state})` : ''}`, - }; + uiFeedbackStream.done(`Connected to mapping service. Processing "${query}"...`); + uiStream.update(); + + // Determine the appropriate tool to call based on query type + const toolName = queryType === 'directions' ? 'get_directions' : 'geocode_location'; + const toolArgs = { + query, + includeMapPreview: includeMap !== false // default to true + }; + + console.log('[GeospatialTool] Calling tool:', toolName, 'with args:', toolArgs); + + const geocodeResultUnknown = await Promise.race([ + mcpClient.callTool({ + name: toolName, + arguments: toolArgs, + }), + new Promise((_, reject) => { + setTimeout(() => reject(new Error('Tool call timeout after 20 seconds')), 20000); + }), + ]); + + console.log('[GeospatialTool] Raw tool result:', geocodeResultUnknown); + + // Narrow the type of geocodeResult + const geocodeResult = geocodeResultUnknown as { tool_results?: Array<{ content?: unknown }> }; + + const toolResults = Array.isArray(geocodeResult.tool_results) ? geocodeResult.tool_results : []; + if (toolResults.length === 0 || !toolResults[0]?.content) { + throw new Error('No content returned from mapping service'); } - console.log('✅ Successfully connected to MCP server.'); - - const geocodeParams = { query, includeMapPreview: true }; - console.log( - '📞 Attempting to call "geocode_location" tool with params:', - geocodeParams - ); - const geocodeResult = await mcp.callTool( - 'geocode_location', - geocodeParams - ); - - if (geocodeResult?.content && Array.isArray(geocodeResult.content)) { - const lastContentItem = - geocodeResult.content[geocodeResult.content.length - 1]; - if ( - lastContentItem?.type === 'text' && - typeof lastContentItem.text === 'string' - ) { - const jsonRegex = /```json\n([\s\S]*?)\n```/; - const match = lastContentItem.text.match(jsonRegex); - if (match?.[1]) { - try { - const parsedJson = JSON.parse(match[1]); - if (parsedJson.location) { - mcpData = { - location: { - latitude: parsedJson.location.latitude, - longitude: parsedJson.location.longitude, - place_name: parsedJson.location.place_name, - address: parsedJson.location.address, - }, - mapUrl: parsedJson.mapUrl, - }; - console.log('✅ Successfully parsed MCP geocode data:', mcpData); - } else { - console.warn( - "⚠️ Parsed JSON from MCP does not contain expected 'location' field." - ); - } - } catch (parseError) { - console.error( - '❌ Error parsing JSON from MCP response:', - parseError, - '\nRaw text was:', - lastContentItem.text - ); - } - } else { - console.warn( - '⚠️ Could not find JSON block in the expected format in MCP response.' - ); + let content = toolResults[0].content; + + // Handle different content formats + if (typeof content === 'string') { + // Try to extract JSON from markdown code blocks + const jsonRegex = /```(?:json)?\n?([\s\S]*?)\n?```/; + const match = content.match(jsonRegex); + if (match) { + content = match[1].trim(); + } + + // Try to parse as JSON + try { + if (typeof content === 'string') { + content = JSON.parse(content); } + } catch (parseError) { + console.warn('[GeospatialTool] Content is not JSON, using as string:', content); + } + } + + // Handle the parsed content + if (typeof content === 'object' && content !== null) { + const parsedData = content as any; + + if (parsedData.location) { + mcpData = { + location: { + latitude: parsedData.location.latitude, + longitude: parsedData.location.longitude, + place_name: parsedData.location.place_name || parsedData.location.name, + address: parsedData.location.address || parsedData.location.formatted_address, + }, + mapUrl: parsedData.mapUrl || parsedData.map_url, + }; } else { - console.warn( - '⚠️ Last content item from MCP is not a text block or is missing.' - ); + throw new Error("Response missing required 'location' field"); } } else { - console.warn( - '⚠️ Geocode result from MCP is not in the expected format (missing content array).', - geocodeResult - ); + throw new Error("Unexpected response format from mapping service"); } - } catch (error) { - console.error('❌ MCP connection or tool call failed:', error); + + uiFeedbackStream.done(`Successfully processed location query for: ${mcpData.location.place_name || query}`); + uiStream.update(); + + } catch (error: any) { + console.error('[GeospatialTool] Tool execution failed:', error.message); + console.error('[GeospatialTool] Error stack:', error.stack); + toolError = `Mapping service error: ${error.message}`; + + uiFeedbackStream.done(toolError); + uiStream.update(); } finally { - if (mcp && mcp.state === 'ready') { - console.log( - '\n[GeospatialTool] MCP client is ready; no explicit close method available for useMcp.' - ); - } + await closeClient(mcpClient); } - // Return a marker object for client-side processing return { type: 'MAP_QUERY_TRIGGER', originalUserInput: query, + queryType: queryType || 'geocode', timestamp: new Date().toISOString(), mcp_response: mcpData, + error: toolError, }; }, -}); +}); \ No newline at end of file diff --git a/lib/agents/tools/index.tsx b/lib/agents/tools/index.tsx index cd3948a1..4c08f373 100644 --- a/lib/agents/tools/index.tsx +++ b/lib/agents/tools/index.tsx @@ -2,15 +2,16 @@ import { createStreamableUI } from 'ai/rsc' import { retrieveTool } from './retrieve' import { searchTool } from './search' import { videoSearchTool } from './video-search' -import { geospatialTool, useGeospatialToolMcp } from './geospatial' +import { geospatialTool } from './geospatial' // Removed useGeospatialToolMcp import export interface ToolProps { uiStream: ReturnType fullResponse: string - mcp?: ReturnType + // mcp?: any; // Removed mcp property as it's no longer passed down for geospatialTool } -export const getTools = ({ uiStream, fullResponse, mcp }: ToolProps) => { +// Removed mcp from parameters +export const getTools = ({ uiStream, fullResponse }: ToolProps) => { const tools: any = { search: searchTool({ uiStream, @@ -20,9 +21,10 @@ export const getTools = ({ uiStream, fullResponse, mcp }: ToolProps) => { uiStream, fullResponse }), + // geospatialTool now only requires uiStream geospatialQueryTool: geospatialTool({ - uiStream, - mcp: mcp || null + uiStream + // mcp: mcp || null // Removed mcp argument }) } diff --git a/lib/auth/get-current-user.ts b/lib/auth/get-current-user.ts index 20c0b24b..6d08ba09 100644 --- a/lib/auth/get-current-user.ts +++ b/lib/auth/get-current-user.ts @@ -6,10 +6,16 @@ import type { User, Session } from '@supabase/supabase-js'; const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; +const AUTH_DISABLED_FLAG = + process.env.AUTH_DISABLED_FOR_DEV === 'true' && + process.env.NODE_ENV !== 'production'; +const MOCK_USER_ID = 'dev-user-001'; // A consistent mock user ID for dev mode + /** * Retrieves the Supabase user and session object in server-side contexts * (Route Handlers, Server Actions, Server Components). * Uses '@supabase/ssr' for cookie-based session management. + * If AUTH_DISABLED_FOR_DEV is true, returns a mock user. * * @returns {Promise<{ user: User | null; session: Session | null; error: any | null }>} */ @@ -18,8 +24,37 @@ export async function getSupabaseUserAndSessionOnServer(): Promise<{ session: Session | null; error: any | null; }> { + if (AUTH_DISABLED_FLAG) { + if (process.env.NODE_ENV === 'development') { + console.log('[Auth] AUTH_DISABLED_FOR_DEV is true. Returning mock user session.'); + } + // Construct a mock user and session object that matches the expected structure + const mockUser: User = { + id: MOCK_USER_ID, + app_metadata: { provider: 'email', providers: ['email'] }, + user_metadata: { name: 'Dev User' }, + aud: 'authenticated', + created_at: new Date().toISOString(), + email: 'dev@example.com', + email_confirmed_at: new Date().toISOString(), + confirmed_at: new Date().toISOString(), + last_sign_in_at: new Date().toISOString(), + role: 'authenticated', + updated_at: new Date().toISOString(), + }; + const mockSession: Session = { + access_token: 'mock-access-token', + refresh_token: 'mock-refresh-token', + expires_in: 3600, + expires_at: Math.floor(Date.now() / 1000) + 3600, + token_type: 'bearer', + user: mockUser, + }; + return { user: mockUser, session: mockSession, error: null }; + } + if (!supabaseUrl || !supabaseAnonKey) { - console.error('Supabase URL or Anon Key is not set for server-side auth.'); + console.error('[Auth] Supabase URL or Anon Key is not set for server-side auth.'); return { user: null, session: null, error: new Error('Missing Supabase environment variables') }; } @@ -35,7 +70,7 @@ export async function getSupabaseUserAndSessionOnServer(): Promise<{ const store = await cookieStore; store.set({ name, value, ...options }); // Set cookie with options } catch (error) { - console.warn(`Failed to set cookie ${name}:`, error); + // console.warn(`[Auth] Failed to set cookie ${name}:`, error); } }, async remove(name: string, options: CookieOptions): Promise { @@ -43,7 +78,7 @@ export async function getSupabaseUserAndSessionOnServer(): Promise<{ const store = await cookieStore; store.set({ name, value: '', ...options, maxAge: 0 }); // Delete cookie by setting maxAge to 0 } catch (error) { - console.warn(`Failed to delete cookie ${name}:`, error); + // console.warn(`[Auth] Failed to delete cookie ${name}:`, error); } }, }, @@ -55,11 +90,12 @@ export async function getSupabaseUserAndSessionOnServer(): Promise<{ } = await supabase.auth.getSession(); if (error) { - console.error('Error getting Supabase session on server:', error.message); + console.error('[Auth] Error getting Supabase session on server:', error.message); return { user: null, session: null, error }; } if (!session) { + // console.log('[Auth] No active Supabase session found.'); return { user: null, session: null, error: null }; } @@ -69,13 +105,23 @@ export async function getSupabaseUserAndSessionOnServer(): Promise<{ /** * Retrieves the current user's ID in server-side contexts. * Wrapper around getSupabaseUserAndSessionOnServer. + * If AUTH_DISABLED_FOR_DEV is true, returns a mock user ID. * - * @returns {Promise} The user ID if a session exists, otherwise null. + * @returns {Promise} The user ID if a session exists or mock is enabled, otherwise null. */ export async function getCurrentUserIdOnServer(): Promise { + if (AUTH_DISABLED_FLAG) { + // This log is helpful for debugging during development + if (process.env.NODE_ENV === 'development') { + console.log(`[Auth] AUTH_DISABLED_FOR_DEV is true. Using mock user ID: ${MOCK_USER_ID}`); + } + return MOCK_USER_ID; + } + const { user, error } = await getSupabaseUserAndSessionOnServer(); if (error) { - return null; // Error already logged in getSupabaseUserAndSessionOnServer + // Error is already logged in getSupabaseUserAndSessionOnServer + return null; } return user?.id || null; } \ No newline at end of file diff --git a/lib/schema/geospatial.ts b/lib/schema/geospatial.ts index 197442fc..e4e2622e 100644 --- a/lib/schema/geospatial.ts +++ b/lib/schema/geospatial.ts @@ -1,5 +1,78 @@ import { z } from 'zod'; export const geospatialQuerySchema = z.object({ - query: z.string().describe("The user's original query or the relevant part of it that pertains to a location, place, distance, directions, or map interaction.") + query: z.string() + .min(1, "Query cannot be empty") + .describe("The user's location-based query - can be an address, place name, landmark, or geographic reference"), + + queryType: z.enum([ + 'geocode', // Find coordinates for a place + 'reverse', // Find place name from coordinates + 'directions', // Get directions between places + 'distance', // Calculate distance between places + 'search', // Search for places/POIs + 'map' // General map request + ]) + .optional() + .default('geocode') + .describe("The type of geospatial operation to perform"), + + includeMap: z.boolean() + .optional() + .default(true) + .describe("Whether to include a map preview/URL in the response"), + + coordinates: z.object({ + latitude: z.number().min(-90).max(90), + longitude: z.number().min(-180).max(180) + }) + .optional() + .describe("Optional coordinates for reverse geocoding or as a reference point"), + + destination: z.string() + .optional() + .describe("Destination for directions queries (when different from main query)"), + + radius: z.number() + .positive() + .optional() + .describe("Search radius in kilometers for place searches"), + + maxResults: z.number() + .int() + .positive() + .max(20) + .optional() + .default(5) + .describe("Maximum number of results to return for search queries") }); + +export type GeospatialQuery = z.infer; + +// Helper function to classify query type based on content +export function classifyGeospatialQuery(query: string): GeospatialQuery['queryType'] { + const lowerQuery = query.toLowerCase(); + + if (lowerQuery.includes('direction') || lowerQuery.includes('route') || lowerQuery.includes('how to get')) { + return 'directions'; + } + + if (lowerQuery.includes('distance') || lowerQuery.includes('how far')) { + return 'distance'; + } + + if (lowerQuery.includes('find') || lowerQuery.includes('search') || lowerQuery.includes('near')) { + return 'search'; + } + + if (lowerQuery.includes('map') || lowerQuery.includes('show me')) { + return 'map'; + } + + // Check if query contains coordinates (lat/lng pattern) + if (/[-]?\d+\.?\d*\s*,\s*[-]?\d+\.?\d*/.test(query)) { + return 'reverse'; + } + + return 'geocode'; +} \ No newline at end of file