From c20b7e4bcb290d25a937699f43a6344d35caff2d Mon Sep 17 00:00:00 2001 From: Kening Date: Fri, 11 Jul 2025 11:23:28 -0700 Subject: [PATCH] fix messge --- Dockerfile | 4 +- package.json | 9 +++ src/app/[...openai]/route.ts | 107 ++++++++++++++++++++++++++++++++--- src/app/page.tsx | 2 +- src/components/LogsList.tsx | 14 ++--- src/middleware.ts | 26 +++++++++ 6 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 src/middleware.ts diff --git a/Dockerfile b/Dockerfile index c8d3d0c..51e065f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:18-alpine WORKDIR /app -RUN apk add --no-cache libc6-compat +RUN apk add --no-cache libc6-compat openssl RUN apk update # Install pnpm @@ -27,4 +27,4 @@ RUN pnpm run build EXPOSE 3000 # Start the application -CMD ["sh", "./start.sh"] \ No newline at end of file +CMD ["sh", "./start.sh"] diff --git a/package.json b/package.json index 1876a3b..f3cc388 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,15 @@ "prisma": { "seed": "tsx prisma/seed.ts" }, + "pnpm": { + "onlyBuiltDependencies": [ + "@prisma/client", + "@prisma/engines", + "esbuild", + "prisma", + "sqlite3" + ] + }, "dependencies": { "@ai-sdk/amazon-bedrock": "^0.0.17", "@ai-sdk/anthropic": "^0.0.46", diff --git a/src/app/[...openai]/route.ts b/src/app/[...openai]/route.ts index 2c023f7..54165bf 100644 --- a/src/app/[...openai]/route.ts +++ b/src/app/[...openai]/route.ts @@ -18,6 +18,33 @@ const openaiClient = new OpenAI({ // Allow streaming responses up to 30 seconds export const maxDuration = 30; +function transformCursorMessages(messages: any[]): any[] { + return messages.map((message) => { + // Handle tool role messages from Cursor + if (message.role === "tool") { + // Transform tool messages to assistant messages with tool results + return { + role: "assistant", + content: message.content || "", + tool_call_id: message.tool_call_id, + name: message.name, + }; + } + + // Handle assistant messages with tool_calls + if (message.role === "assistant" && message.tool_calls) { + return { + role: "assistant", + content: message.content || "", + tool_calls: message.tool_calls, + }; + } + + // Pass through other messages as-is + return message; + }); +} + async function getAIModelClient(provider: string, model: string) { switch (provider.toLowerCase()) { case "openai": @@ -66,11 +93,18 @@ export async function POST( { params }: { params: { openai: string[] } }, ) { const endpoint = params.openai.join("/"); + console.log("POST request received:", { + endpoint, + url: request.url, + headers: Object.fromEntries(request.headers), + }); + if (endpoint !== "chat/completions" && endpoint !== "v1/chat/completions") { return NextResponse.json({ error: "Not found", endpoint }, { status: 404 }); } const body = await request.json(); + console.log("Request body:", JSON.stringify(body, null, 2)); const { messages, model: cursorModel, stream = false, ...otherParams } = body; try { @@ -96,7 +130,8 @@ export async function POST( const aiModel = await getAIModelClient(provider, model); - let modifiedMessages = messages; + // Transform Cursor messages to AI SDK format + let modifiedMessages = transformCursorMessages(messages); if (provider.toLowerCase() === "anthropiccached") { const hasPotentialContext = messages.some( @@ -237,6 +272,9 @@ export async function POST( "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization, x-api-key, ngrok-skip-browser-warning", }, }); } @@ -267,7 +305,13 @@ export async function POST( }; await insertLog(logEntry); - return NextResponse.json(result); + return NextResponse.json(result, { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization, x-api-key, ngrok-skip-browser-warning", + }, + }); } catch (error) { console.error("Error in chat completion:", error); const errorMessage = error instanceof Error ? error.message : String(error); @@ -293,28 +337,48 @@ export async function GET( { params }: { params: { openai: string[] } }, ) { const endpoint = params.openai.join("/"); - - // Existing 'models' endpoint - if (endpoint === "models") { + console.log("GET request received:", { + endpoint, + url: request.url, + headers: Object.fromEntries(request.headers), + }); + + // Handle both 'models' and 'v1/models' endpoints + if (endpoint === "models" || endpoint === "v1/models") { const logEntry = { method: "GET", - url: "/api/v1/models", + url: `/api/${endpoint}`, headers: Object.fromEntries(request.headers), body: {}, response: {}, timestamp: new Date(), + metadata: {}, // Add empty metadata object to satisfy Prisma schema }; try { const models = await openaiClient.models.list(); logEntry.response = models; await insertLog(logEntry); - return NextResponse.json(models); + return NextResponse.json(models, { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization, x-api-key, ngrok-skip-browser-warning", + }, + }); } catch (error) { console.error("Error fetching models:", error); logEntry.response = { error: String(error) }; + logEntry.metadata = { error: String(error) }; // Add error to metadata await insertLog(logEntry); - return NextResponse.json({ error: String(error) }, { status: 500 }); + return NextResponse.json({ error: String(error) }, { + status: 500, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization, x-api-key, ngrok-skip-browser-warning", + }, + }); } } @@ -333,7 +397,9 @@ export async function GET( return testGroq(); } - return NextResponse.json({ error: "Not found" }, { status: 404 }); + // Log any unmatched endpoints + console.log("Unmatched GET endpoint:", endpoint); + return NextResponse.json({ error: "Not found", endpoint }, { status: 404 }); } async function testOpenAI() { @@ -436,3 +502,26 @@ async function testGroq() { return NextResponse.json({ error: String(error) }, { status: 500 }); } } + +// Handle OPTIONS requests for CORS +export async function OPTIONS( + request: NextRequest, + { params }: { params: { openai: string[] } }, +) { + const endpoint = params.openai.join("/"); + console.log("OPTIONS request received:", { + endpoint, + url: request.url, + headers: Object.fromEntries(request.headers), + }); + + return new NextResponse(null, { + status: 204, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization, x-api-key, ngrok-skip-browser-warning", + "Access-Control-Max-Age": "86400", + }, + }); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 99ac6af..8f4d8b1 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -71,7 +71,7 @@ export default function Home() { setLogs(logsData as unknown as Log[]); // Type assertion setStats(statsData); setAIConfigurations(configData as AIConfiguration[]); // Type assertion - const defaultConfig = configData.find((config) => config.isDefault); + const defaultConfig = configData?.find((config) => config.isDefault); setSelectedConfig(defaultConfig ? defaultConfig.name : ""); setLoading(false); diff --git a/src/components/LogsList.tsx b/src/components/LogsList.tsx index 8f52536..ce143fb 100644 --- a/src/components/LogsList.tsx +++ b/src/components/LogsList.tsx @@ -42,16 +42,16 @@ const LogsListComponent: React.FC = ({ return (
{logs.map((log) => { - const totalTokens = log.metadata.totalTokens || 0; - const totalCost = log.metadata.totalCost || 0; + const totalTokens = log.metadata?.totalTokens || 0; + const totalCost = log.metadata?.totalCost || 0; const firstUserMessage = - log.body.messages.find((m) => m.role === "user" && !("name" in m)) + log.body?.messages?.find((m) => m.role === "user" && !("name" in m)) ?.content || "No message available"; const truncatedMessage = firstUserMessage.slice(0, 100) + (firstUserMessage.length > 100 ? "..." : ""); const isSelected = selectedLogId === log.id; - const providerColorClass = getProviderColor(log.metadata.provider); + const providerColorClass = getProviderColor(log.metadata?.provider || "other"); return ( = ({
- {log.metadata.provider} + {log.metadata?.provider || "unknown"} - {log.metadata.model} + {log.metadata?.model || "unknown"}
diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..2ed7d71 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,26 @@ +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +export function middleware(request: NextRequest) { + console.log("=== INCOMING REQUEST ==="); + console.log("Method:", request.method); + console.log("URL:", request.url); + console.log("Path:", new URL(request.url).pathname); + console.log("Headers:", Object.fromEntries(request.headers)); + console.log("======================="); + + return NextResponse.next(); +} + +// Configure which paths the middleware runs on +export const config = { + matcher: [ + /* + * Match all request paths except for the ones starting with: + * - _next/static (static files) + * - _next/image (image optimization files) + * - favicon.ico (favicon file) + */ + '/((?!_next/static|_next/image|favicon.ico).*)', + ], +} \ No newline at end of file