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
170 lines (151 loc) · 6.02 KB
/
Copy pathroute.ts
File metadata and controls
170 lines (151 loc) · 6.02 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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { NextRequest, NextResponse } from "next/server";
import {
CopilotRuntime,
ExperimentalEmptyAdapter,
copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";
import { AbstractAgent, HttpAgent } from "@ag-ui/client";
// The agent backend runs as a separate process on port 8000.
// This runtime proxies CopilotKit requests to it via AG-UI protocol.
const AGENT_URL = process.env.AGENT_URL || "http://localhost:8000";
console.log("[copilotkit/route] Initializing CopilotKit runtime");
console.log(`[copilotkit/route] AGENT_URL: ${AGENT_URL}`);
function createMainAgent() {
return new HttpAgent({ url: `${AGENT_URL}/agui` });
}
function createReasoningAgent() {
return new HttpAgent({ url: `${AGENT_URL}/reasoning/agui` });
}
// State-aware agents are served by a custom AGUI handler in agent_server.py
// that emits StateSnapshotEvent after every run. Stock Agno AGUI does NOT
// emit state events, so demos that depend on agent-side state writes
// (set_notes, delegations) must point at these dedicated routes.
function createSharedStateRWAgent() {
return new HttpAgent({ url: `${AGENT_URL}/shared-state-rw/agui` });
}
function createSubagentsAgent() {
return new HttpAgent({ url: `${AGENT_URL}/subagents/agui` });
}
// gen-ui-agent: agent owns its own state schema (`steps`) and mutates it
// via the `set_steps` tool. Needs the state-aware AGUI router so the
// frontend's `useAgent({ updates: [OnStateChanged] })` receives the
// StateSnapshotEvent each run — stock Agno AGUI does not emit one.
function createGenUiAgent() {
return new HttpAgent({ url: `${AGENT_URL}/gen-ui-agent/agui` });
}
// Main agent backs most demos. The Next.js runtime aliases the single
// Agno `main` agent under every demo cell name so per-cell frontend
// tool/component registrations scope correctly.
const mainAgentNames = [
"agentic_chat",
"human_in_the_loop",
"hitl-in-chat",
"hitl-in-app",
"tool-rendering",
"tool-rendering-default-catchall",
"tool-rendering-custom-catchall",
"gen-ui-tool-based",
"shared-state-read",
"shared-state-write",
"shared-state-streaming",
// Neutral / chrome demos reusing the default agent.
"prebuilt-sidebar",
"prebuilt-popup",
"chat-slots",
"chat-customization-css",
"headless-simple",
"headless-complete",
"frontend_tools",
"frontend-tools-async",
"readonly-state-agent-context",
"agent-config",
];
// Interrupt-adapted demos: gen-ui-interrupt and interrupt-headless share
// the same Agno scheduling agent at /interrupt-adapted/agui. The agent has
// tools=[]; `schedule_meeting` is provided by the frontend via
// `useFrontendTool` with an async Promise handler.
function createInterruptAgent() {
return new HttpAgent({ url: `${AGENT_URL}/interrupt-adapted/agui` });
}
const interruptAgentNames = ["gen-ui-interrupt", "interrupt-headless"];
// Reasoning agent names — backed by the reasoning-enabled Agno agent at
// /reasoning/agui. Emits AG-UI REASONING_MESSAGE_* events that the
// frontend renders via CopilotChatReasoningMessage (or a custom slot).
const reasoningAgentNames = [
"agentic-chat-reasoning",
"reasoning-default-render",
"tool-rendering-reasoning-chain",
];
const agents: Record<string, AbstractAgent> = {};
for (const name of mainAgentNames) {
agents[name] = createMainAgent();
}
for (const name of interruptAgentNames) {
agents[name] = createInterruptAgent();
}
for (const name of reasoningAgentNames) {
agents[name] = createReasoningAgent();
}
// Bidirectional shared-state agent — UI writes preferences, agent writes
// notes back via set_notes and the custom AGUI router emits a
// StateSnapshotEvent that the frontend's useAgent picks up.
// gen-ui-agent — owns its own `steps` state schema and mutates it via the
// `set_steps` tool. Routes to the state-aware AGUI handler that emits a
// StateSnapshotEvent each run so the frontend's progress card updates.
agents["gen-ui-agent"] = createGenUiAgent();
agents["shared-state-read-write"] = createSharedStateRWAgent();
// Sub-agents supervisor — appends to state["delegations"] every time a
// research / writing / critique sub-agent is delegated to. Same custom
// AGUI router emits the StateSnapshotEvent needed for the live log.
agents["subagents"] = createSubagentsAgent();
agents["default"] = createMainAgent();
console.log(
`[copilotkit/route] Registered ${Object.keys(agents).length} agent names: ${Object.keys(agents).join(", ")}`,
);
export const POST = async (req: NextRequest) => {
const url = req.url;
const contentType = req.headers.get("content-type");
console.log(`[copilotkit/route] POST ${url} (content-type: ${contentType})`);
try {
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
endpoint: "/api/copilotkit",
serviceAdapter: new ExperimentalEmptyAdapter(),
runtime: new CopilotRuntime({
// @ts-ignore -- Published CopilotRuntime agents type wraps Record in MaybePromise<NonEmptyRecord<...>> which rejects plain Records; fixed in source, pending release
agents,
}),
});
const response = await handleRequest(req);
console.log(`[copilotkit/route] Response status: ${response.status}`);
return response;
} catch (error: unknown) {
const err = error as Error;
console.error(`[copilotkit/route] ERROR: ${err.message}`);
console.error(`[copilotkit/route] Stack: ${err.stack}`);
return NextResponse.json(
{ error: err.message, stack: err.stack },
{ status: 500 },
);
}
};
export const GET = async () => {
console.log("[copilotkit/route] GET /api/copilotkit (health probe)");
let agentStatus = "unknown";
try {
const res = await fetch(`${AGENT_URL}/health`, {
signal: AbortSignal.timeout(3000),
});
agentStatus = res.ok ? "reachable" : `error (${res.status})`;
} catch (e: unknown) {
agentStatus = `unreachable (${(e as Error).message})`;
}
return NextResponse.json({
status: "ok",
agent_url: AGENT_URL,
agent_status: agentStatus,
env: {
OPENAI_API_KEY: process.env.OPENAI_API_KEY ? "set" : "NOT SET",
NODE_ENV: process.env.NODE_ENV,
},
});
};