Nicholai 7f5efb84e2 feat(agent): migrate to Anthropic Agents SDK
Replace Vercel AI SDK with Anthropic Claude Agent SDK.
Add standalone agent server (packages/agent-server/)
with MCP tools, JWT auth, and SSE streaming. Introduce
bridge API routes (src/app/api/compass/) and custom
SSE hooks (use-agent, use-compass-chat) replacing
useChat. Remove provider.ts, tools.ts, system-prompt.ts,
github-tools.ts, usage.ts, and old agent route.
2026-02-16 18:37:26 -07:00

156 lines
4.0 KiB
TypeScript

#!/usr/bin/env bun
/**
* Compass Agent Server
* Standalone Node.js server wrapping Anthropic Agent SDK
*/
import { config } from "./config"
import { validateAuth } from "./auth"
import { getOrCreateSession } from "./sessions"
import { createAgentStream } from "./stream"
interface ChatRequest {
messages: Array<{
role: "user" | "assistant"
content: string
}>
}
/**
* Handle POST /agent/chat
*/
async function handleChat(request: Request): Promise<Response> {
// Validate auth
const authResult = await validateAuth(request)
if ("error" in authResult) {
return new Response(
JSON.stringify({ error: authResult.error }),
{ status: 401, headers: { "Content-Type": "application/json" } }
)
}
// Extract JWT token from Authorization header
const authHeader = request.headers.get("authorization")
const authToken = authHeader?.slice(7) || "" // Remove "Bearer " prefix
// Extract headers
const sessionId = request.headers.get("x-session-id") || crypto.randomUUID()
const currentPage = request.headers.get("x-current-page") || "/"
const timezone = request.headers.get("x-timezone") || "UTC"
const model = request.headers.get("x-model") || "sonnet"
// Get or create session
getOrCreateSession(sessionId)
// Parse body
let body: ChatRequest
try {
body = await request.json()
} catch {
return new Response(
JSON.stringify({ error: "Invalid JSON body" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
)
}
if (!Array.isArray(body.messages) || body.messages.length === 0) {
return new Response(
JSON.stringify({ error: "messages array is required and cannot be empty" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
)
}
// Create SSE stream
const stream = await createAgentStream(body.messages, {
auth: authResult,
authToken,
sessionId,
currentPage,
timezone,
provider: authResult.provider,
model,
})
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Access-Control-Allow-Origin": request.headers.get("origin") || "*",
"Access-Control-Allow-Credentials": "true",
},
})
}
/**
* Handle GET /health
*/
function handleHealth(): Response {
return new Response(
JSON.stringify({ status: "ok", version: "0.1.0" }),
{ headers: { "Content-Type": "application/json" } }
)
}
/**
* CORS preflight handler
*/
function handlePreflight(request: Request): Response {
const origin = request.headers.get("origin") || ""
const allowed = config.allowedOrigins.includes(origin) || config.allowedOrigins.includes("*")
if (!allowed) {
return new Response(null, { status: 403 })
}
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Authorization, Content-Type, x-session-id, x-current-page, x-timezone, x-model",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Max-Age": "86400",
},
})
}
/**
* Main request router
*/
async function handleRequest(request: Request): Promise<Response> {
const url = new URL(request.url)
// Handle CORS preflight
if (request.method === "OPTIONS") {
return handlePreflight(request)
}
// Route requests
if (url.pathname === "/health" && request.method === "GET") {
return handleHealth()
}
if (url.pathname === "/agent/chat" && request.method === "POST") {
return handleChat(request)
}
return new Response(
JSON.stringify({ error: "Not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } }
)
}
/**
* Start the server
*/
const server = Bun.serve({
port: config.port,
async fetch(request) {
return handleRequest(request)
},
})
console.log(`Agent server running on http://localhost:${server.port}`)
console.log(`Allowed origins: ${config.allowedOrigins.join(", ")}`)