forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroute.ts
More file actions
149 lines (136 loc) · 6.04 KB
/
Copy pathroute.ts
File metadata and controls
149 lines (136 loc) · 6.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import {
CopilotRuntime,
ExperimentalEmptyAdapter,
copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";
import { LangGraphAgent } from "@copilotkit/runtime/langgraph";
import { NextRequest, NextResponse } from "next/server";
// 1. Runtime adapter. Since this starter wires agent responses directly
// (no OpenAI/Anthropic-compatible chat-completions fallback), we use
// the empty adapter: it satisfies the runtime interface without
// dispatching any LLM requests of its own.
const serviceAdapter = new ExperimentalEmptyAdapter();
// 2. LANGGRAPH_DEPLOYMENT_URL handling. In development we fall back to
// localhost:8125 (the port this starter's LangGraph dev server uses —
// see apps/agent/package.json) and warn so the developer sees it. In
// production a missing URL is almost always a misconfiguration — but
// we defer the hard failure to the first request (see POST below)
// rather than throwing at module load. `next build` evaluates route
// modules with NODE_ENV=production during route collection, while
// runtime-only env vars (Vercel secrets, Railway env, Docker ENV at
// container start) are NOT injected at build time. Throwing at module
// load would abort otherwise-valid production builds.
if (
!process.env.LANGGRAPH_DEPLOYMENT_URL &&
process.env.NODE_ENV !== "production"
) {
console.warn(
"[copilotkit/route] LANGGRAPH_DEPLOYMENT_URL is not set; falling back to http://localhost:8125. Set LANGGRAPH_DEPLOYMENT_URL in production.",
);
}
// LangSmith is optional; warn once at module load so a missing key
// surfaces in logs. When absent we omit the langsmithApiKey field
// entirely rather than passing "" — omitting the field disables
// LangSmith tracing cleanly; passing an empty string may be forwarded
// to the SDK. Gate the warn on NODE_ENV so it does not fire during
// `next build` in CI (same rationale as the DEPLOYMENT_URL warn above).
if (!process.env.LANGSMITH_API_KEY && process.env.NODE_ENV !== "production") {
console.warn(
"[copilotkit/route] LANGSMITH_API_KEY is not set; LangSmith tracing is disabled for this session.",
);
}
// 3. Lazy runtime construction. We build the LangGraphAgent + runtime +
// handler on the first request rather than at module load, so that
// `next build` (which evaluates this file with NODE_ENV=production but
// without runtime env vars) does not bake the fallback localhost URL
// into the compiled artifact. The cached closure keeps per-request
// overhead at the same single-allocation cost as the eager form.
// Matches the signature returned by copilotRuntimeNextJSAppRouterEndpoint:
// `(req: Request) => Response | Promise<Response>`. NextRequest extends
// Request, so the POST handler below can still pass its req through
// without an explicit cast.
let cachedHandleRequest:
| ((req: Request) => Response | Promise<Response>)
| null = null;
const getHandleRequest = () => {
if (cachedHandleRequest) return cachedHandleRequest;
const deploymentUrl = process.env.LANGGRAPH_DEPLOYMENT_URL;
if (!deploymentUrl && process.env.NODE_ENV === "production") {
throw new Error(
"[copilotkit/route] LANGGRAPH_DEPLOYMENT_URL is required in production. " +
"Set it to the deployed LangGraph endpoint for this starter.",
);
}
const agent = new LangGraphAgent({
deploymentUrl: deploymentUrl || "http://localhost:8125",
graphId: "default",
// Only pass langsmithApiKey when it's a non-empty string; omitting
// the field disables LangSmith tracing without asking the SDK to
// authenticate with an empty key.
...(process.env.LANGSMITH_API_KEY
? { langsmithApiKey: process.env.LANGSMITH_API_KEY }
: {}),
});
// Register the single LangGraph agent under the `default` name, which
// matches the <CopilotKit agent="default"> prop in layout.tsx. If you
// need to expose this agent under an additional id (e.g. when pointing
// a second frontend at this runtime), add another entry here and
// update the corresponding <CopilotKit agent="..."> prop.
const runtime = new CopilotRuntime({
agents: {
default: agent,
},
});
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime,
serviceAdapter,
endpoint: "/api/copilotkit",
});
cachedHandleRequest = handleRequest;
return handleRequest;
};
// 4. POST handler. Resolves the cached handler (building it on first
// request), then dispatches. Wrap in try/catch so unhandled exceptions
// surface as a structured 500 rather than a raw Next.js error page,
// and log the failure for observability. Errors inside the streaming
// response are handled by the runtime itself.
export const POST = async (req: NextRequest) => {
// Redact `detail` in production: raw error messages can leak
// internals (stack-adjacent strings, paths, env-var names). Keep
// the full detail in non-production builds so developers see the
// real cause locally.
const isProd = process.env.NODE_ENV === "production";
// Split construction vs dispatch into two try/catch regions so
// logs distinguish runtime-construction failures (bad config / env)
// from dispatch failures inside handleRequest. Response shape is
// unchanged between the two.
let handleRequest: (req: Request) => Response | Promise<Response>;
try {
handleRequest = getHandleRequest();
} catch (err) {
console.error("[copilotkit/route] runtime construction failed:", err);
return NextResponse.json(
{
error: "Internal error while dispatching CopilotKit request.",
...(isProd
? {}
: { detail: err instanceof Error ? err.message : String(err) }),
},
{ status: 500 },
);
}
try {
return await handleRequest(req);
} catch (err) {
console.error("[copilotkit/route] handleRequest dispatch failed:", err);
return NextResponse.json(
{
error: "Internal error while dispatching CopilotKit request.",
...(isProd
? {}
: { detail: err instanceof Error ? err.message : String(err) }),
},
{ status: 500 },
);
}
};