Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 6 additions & 20 deletions frontend/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,12 @@ const config = {
devIndicators: false,
async rewrites() {
const rewrites = [];
const fallback = [];
const gatewayURL = getInternalServiceURL(
"DEER_FLOW_INTERNAL_GATEWAY_BASE_URL",
"http://127.0.0.1:8001",
);

if (!process.env.NEXT_PUBLIC_LANGGRAPH_BASE_URL) {
rewrites.push({
source: "/api/langgraph",
destination: `${gatewayURL}/api`,
});
rewrites.push({
source: "/api/langgraph/:path*",
destination: `${gatewayURL}/api/:path*`,
});
}

if (!process.env.NEXT_PUBLIC_BACKEND_BASE_URL) {
rewrites.push({
source: "/api/agents",
Expand All @@ -57,20 +47,16 @@ const config = {
destination: `${gatewayURL}/api/skills/:path*`,
});

// Catch-all for remaining gateway API routes (models, threads, memory,
// mcp, artifacts, uploads, suggestions, runs, etc.) that don't have
// their own NEXT_PUBLIC_* env var toggle.
//
// NOTE: this must come AFTER the /api/langgraph rewrite above so that
// LangGraph-compatible routes keep their public prefix while Gateway
// receives its native /api/* paths.
rewrites.push({
fallback.push({
source: "/api/:path*",
destination: `${gatewayURL}/api/:path*`,
});
}

return rewrites;
return {
beforeFiles: rewrites,
fallback,
};
},
};

Expand Down
109 changes: 109 additions & 0 deletions frontend/src/app/api/langgraph/[[...path]]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { type NextRequest } from "next/server";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

const DEFAULT_GATEWAY_BASE_URL = "http://127.0.0.1:8001";

const HOP_BY_HOP_HEADERS = [
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade",
] as const;

function getGatewayBaseUrl() {
const configured = process.env.DEER_FLOW_INTERNAL_GATEWAY_BASE_URL?.trim();
return (
configured && configured.length > 0 ? configured : DEFAULT_GATEWAY_BASE_URL
).replace(/\/+$/, "");
}

function getConnectionHeaderNames(headers: Headers) {
return (
headers
.get("connection")
?.split(",")
.map((name) => name.trim().toLowerCase())
.filter(Boolean) ?? []
);
}

function deleteHopByHopHeaders(
headers: Headers,
additionalNames: string[] = [],
) {
for (const name of [...HOP_BY_HOP_HEADERS, ...additionalNames]) {
headers.delete(name);
}
}

function buildGatewayUrl(path: string[] | undefined, search: string) {
const pathname = ["api", ...(path ?? []).map(encodeURIComponent)].join("/");
return `${getGatewayBaseUrl()}/${pathname}${search}`;
}

function buildHeaders(request: NextRequest, isStreamRequest: boolean) {
const headers = new Headers(request.headers);
deleteHopByHopHeaders(headers, getConnectionHeaderNames(headers));
for (const name of ["host", "content-length"]) {
headers.delete(name);
}
if (isStreamRequest) {
headers.set("accept-encoding", "identity");
}
return headers;
}

async function proxyRequest(
request: NextRequest,
context: { params: Promise<{ path?: string[] }> },
) {
if (process.env.NEXT_PUBLIC_LANGGRAPH_BASE_URL) {
return new Response(null, { status: 404 });
}

// Keep LangGraph SSE out of Next rewrites because the dev proxy can buffer
// events on Windows. Returning the upstream body directly preserves streaming.
const { path } = await context.params;
const target = buildGatewayUrl(path, request.nextUrl.search);
const method = request.method.toUpperCase();
const hasBody = !["GET", "HEAD", "OPTIONS"].includes(method);
const isStreamRequest = path?.at(-1) === "stream";
const init: RequestInit & { duplex?: "half" } = {
method,
headers: buildHeaders(request, isStreamRequest),
redirect: "manual",
cache: "no-store",
signal: request.signal,
};

if (hasBody) {
init.body = request.body;
init.duplex = "half";
}

const upstream = await fetch(target, init);
const headers = new Headers(upstream.headers);
deleteHopByHopHeaders(headers, getConnectionHeaderNames(headers));
headers.delete("content-length");
headers.set("X-Accel-Buffering", "no");

return new Response(upstream.body, {
status: upstream.status,
statusText: upstream.statusText,
headers,
});
}

export const GET = proxyRequest;
export const HEAD = proxyRequest;
export const POST = proxyRequest;
export const PUT = proxyRequest;
export const PATCH = proxyRequest;
export const DELETE = proxyRequest;
export const OPTIONS = proxyRequest;
Loading
Loading