|
1 | 1 | import { createFileRoute } from '@tanstack/react-router' |
2 | | -<% if (addOnEnabled['netlify']) { %>import { createAnthropic } from '@ai-sdk/anthropic' |
3 | | -<% } else { %>import { anthropic } from '@ai-sdk/anthropic' |
4 | | -<% } %>import { convertToModelMessages, stepCountIs, streamText } from 'ai' |
| 2 | +import { chat, maxIterations, toStreamResponse } from '@tanstack/ai' |
| 3 | +import { anthropic } from '@tanstack/ai-anthropic' |
5 | 4 |
|
6 | | -import getTools from '@/utils/demo.tools' |
| 5 | +import { getGuitars, recommendGuitarToolDef } from '@/lib/example.guitar-tools' |
7 | 6 |
|
8 | 7 | const SYSTEM_PROMPT = `You are a helpful assistant for a store that sells guitars. |
9 | 8 |
|
10 | | -You can use the following tools to help the user: |
| 9 | +CRITICAL INSTRUCTIONS - YOU MUST FOLLOW THIS EXACT WORKFLOW: |
11 | 10 |
|
12 | | -- getGuitars: Get all guitars from the database |
13 | | -- recommendGuitar: Recommend a guitar to the user |
| 11 | +When a user asks for a guitar recommendation: |
| 12 | +1. FIRST: Use the getGuitars tool (no parameters needed) |
| 13 | +2. SECOND: Use the recommendGuitar tool with the ID of the guitar you want to recommend |
| 14 | +3. NEVER write a recommendation directly - ALWAYS use the recommendGuitar tool |
| 15 | + |
| 16 | +IMPORTANT: |
| 17 | +- The recommendGuitar tool will display the guitar in a special, appealing format |
| 18 | +- You MUST use recommendGuitar for ANY guitar recommendation |
| 19 | +- ONLY recommend guitars from our inventory (use getGuitars first) |
| 20 | +- The recommendGuitar tool has a buy button - this is how customers purchase |
| 21 | +- Do NOT describe the guitar yourself - let the recommendGuitar tool do it |
14 | 22 | ` |
15 | | -<% if (addOnEnabled['netlify']) { %>const anthropic = createAnthropic({ |
16 | | - baseURL: process.env.ANTHROPIC_BASE_URL |
17 | | - ? `${process.env.ANTHROPIC_BASE_URL}/v1` |
18 | | - : undefined, |
19 | | - apiKey: process.env.ANTHROPIC_API_KEY, |
20 | | - headers: { |
21 | | - 'user-agent': 'anthropic/', |
22 | | - }, |
23 | | -}) |
24 | | -<% } %> |
25 | | -export const Route = createFileRoute('/api/demo-chat')({ |
| 23 | + |
| 24 | +export const Route = createFileRoute('/demo/api/tanchat')({ |
26 | 25 | server: { |
27 | 26 | handlers: { |
28 | 27 | POST: async ({ request }) => { |
| 28 | + // Capture request signal before reading body (it may be aborted after body is consumed) |
| 29 | + const requestSignal = request.signal |
| 30 | + |
| 31 | + // If request is already aborted, return early |
| 32 | + if (requestSignal.aborted) { |
| 33 | + return new Response(null, { status: 499 }) // 499 = Client Closed Request |
| 34 | + } |
| 35 | + |
| 36 | + const abortController = new AbortController() |
| 37 | + |
29 | 38 | try { |
30 | 39 | const { messages } = await request.json() |
31 | 40 |
|
32 | | - const tools = await getTools() |
33 | | - |
34 | | - const result = await streamText({ |
35 | | - model: anthropic('<% if (addOnEnabled['netlify']) { %>claude-sonnet-4-5-20250929<% } else { %>claude-haiku-4-5<% } %>'), |
36 | | - messages: convertToModelMessages(messages), |
37 | | - temperature: 0.7, |
38 | | - stopWhen: stepCountIs(5), |
39 | | - system: SYSTEM_PROMPT, |
40 | | - tools, |
| 41 | + const stream = chat({ |
| 42 | + adapter: anthropic(), |
| 43 | + model: 'claude-haiku-4-5', |
| 44 | + tools: [ |
| 45 | + getGuitars, // Server tool |
| 46 | + recommendGuitarToolDef, // No server execute - client will handle |
| 47 | + ], |
| 48 | + systemPrompts: [SYSTEM_PROMPT], |
| 49 | + agentLoopStrategy: maxIterations(5), |
| 50 | + messages, |
| 51 | + abortController, |
41 | 52 | }) |
42 | 53 |
|
43 | | - return result.toUIMessageStreamResponse() |
44 | | - } catch (error) { |
| 54 | + return toStreamResponse(stream, { abortController }) |
| 55 | + } catch (error: any) { |
45 | 56 | console.error('Chat API error:', error) |
| 57 | + // If request was aborted, return early (don't send error response) |
| 58 | + if (error.name === 'AbortError' || abortController.signal.aborted) { |
| 59 | + return new Response(null, { status: 499 }) // 499 = Client Closed Request |
| 60 | + } |
46 | 61 | return new Response( |
47 | 62 | JSON.stringify({ error: 'Failed to process chat request' }), |
48 | 63 | { |
|
0 commit comments