import { createFileRoute } from '@tanstack/react-router' import { chat, maxIterations, toServerSentEventsResponse } from '@tanstack/ai' import { anthropicText } from '@tanstack/ai-anthropic' import { openaiText } from '@tanstack/ai-openai' import { geminiText } from '@tanstack/ai-gemini' import { ollamaText } from '@tanstack/ai-ollama' import { getGuitars, recommendGuitarToolDef } from '#/lib/demo-guitar-tools' const SYSTEM_PROMPT = `You are a helpful assistant for a store that sells guitars. CRITICAL INSTRUCTIONS - YOU MUST FOLLOW THIS EXACT WORKFLOW: When a user asks for a guitar recommendation: 1. FIRST: Use the getGuitars tool (no parameters needed) 2. SECOND: Use the recommendGuitar tool with the ID of the guitar you want to recommend 3. NEVER write a recommendation directly - ALWAYS use the recommendGuitar tool IMPORTANT: - The recommendGuitar tool will display the guitar in a special, appealing format - You MUST use recommendGuitar for ANY guitar recommendation - ONLY recommend guitars from our inventory (use getGuitars first) - The recommendGuitar tool has a buy button - this is how customers purchase - Do NOT describe the guitar yourself - let the recommendGuitar tool do it ` export const Route = createFileRoute('/demo/api/ai/chat')({ server: { handlers: { POST: async ({ request }) => { // Capture request signal before reading body (it may be aborted after body is consumed) const requestSignal = request.signal // If request is already aborted, return early if (requestSignal.aborted) { return new Response(null, { status: 499 }) // 499 = Client Closed Request } const abortController = new AbortController() try { const body = await request.json() const { messages } = body // Determine the best available provider let provider: string = 'ollama' let model: string = 'mistral:7b' if (process.env.ANTHROPIC_API_KEY) { provider = 'anthropic' model = 'claude-haiku-4-5' } else if (process.env.OPENAI_API_KEY) { provider = 'openai' model = 'gpt-4o' } else if (process.env.GEMINI_API_KEY) { provider = 'gemini' model = 'gemini-2.0-flash-exp' } // Adapter factory pattern for multi-vendor support const adapterConfig = { anthropic: () => anthropicText((model || 'claude-haiku-4-5') as any), openai: () => openaiText((model || 'gpt-4o') as any), gemini: () => geminiText((model || 'gemini-2.0-flash-exp') as any), ollama: () => ollamaText((model || 'mistral:7b') as any), } const adapter = adapterConfig[provider]() const stream = chat({ adapter, tools: [ getGuitars, // Server tool recommendGuitarToolDef, // No server execute - client will handle ], systemPrompts: [SYSTEM_PROMPT], agentLoopStrategy: maxIterations(5), messages, abortController, }) return toServerSentEventsResponse(stream, { abortController }) } catch (error: any) { // If request was aborted, return early (don't send error response) if (error.name === 'AbortError' || abortController.signal.aborted) { return new Response(null, { status: 499 }) // 499 = Client Closed Request } return new Response( JSON.stringify({ error: 'Failed to process chat request' }), { status: 500, headers: { 'Content-Type': 'application/json' }, }, ) } }, }, }, })