@alexanderolsen/create-deepagent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +661 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/registry/frameworks/deno/.env.example +6 -0
- package/registry/frameworks/deno/README.md +137 -0
- package/registry/frameworks/deno/client/index.html +23 -0
- package/registry/frameworks/deno/client/package.json +30 -0
- package/registry/frameworks/deno/client/public/favicon.ico +0 -0
- package/registry/frameworks/deno/client/src/components/Chat.tsx +124 -0
- package/registry/frameworks/deno/client/src/components/ChatApp.tsx +129 -0
- package/registry/frameworks/deno/client/src/components/Conversation.tsx +91 -0
- package/registry/frameworks/deno/client/src/components/MessageBubbles.tsx +88 -0
- package/registry/frameworks/deno/client/src/components/MessageReasoning.tsx +71 -0
- package/registry/frameworks/deno/client/src/components/MessageThread.tsx +135 -0
- package/registry/frameworks/deno/client/src/components/StreamingIndicator.tsx +36 -0
- package/registry/frameworks/deno/client/src/components/Subagents.tsx +120 -0
- package/registry/frameworks/deno/client/src/components/ThemeIcons.tsx +31 -0
- package/registry/frameworks/deno/client/src/components/ThreadHistory.tsx +73 -0
- package/registry/frameworks/deno/client/src/components/ToolCall.tsx +89 -0
- package/registry/frameworks/deno/client/src/lib/agent-type.ts +4 -0
- package/registry/frameworks/deno/client/src/lib/chat/threads-client.ts +51 -0
- package/registry/frameworks/deno/client/src/main.tsx +11 -0
- package/registry/frameworks/deno/client/src/styles/globals.css +714 -0
- package/registry/frameworks/deno/client/src/vite-env.d.ts +1 -0
- package/registry/frameworks/deno/client/tsconfig.app.json +7 -0
- package/registry/frameworks/deno/client/tsconfig.json +24 -0
- package/registry/frameworks/deno/client/tsconfig.node.json +19 -0
- package/registry/frameworks/deno/client/vite.config.ts +24 -0
- package/registry/frameworks/deno/deno.json +16 -0
- package/registry/frameworks/deno/main.ts +23 -0
- package/registry/frameworks/deno/package.json +14 -0
- package/registry/frameworks/deno/server/agent/index.ts +60 -0
- package/registry/frameworks/deno/server/agent/middleware.ts +24 -0
- package/registry/frameworks/deno/server/agent/tools.ts +64 -0
- package/registry/frameworks/deno/server/registry.ts +40 -0
- package/registry/frameworks/deno/server/routes.ts +114 -0
- package/registry/frameworks/deno/server/serialize.ts +30 -0
- package/registry/frameworks/deno/server/session.ts +210 -0
- package/registry/frameworks/deno/server/threads.ts +404 -0
- package/registry/frameworks/deno.ts +17 -0
- package/registry/frameworks/hono/.env.example +6 -0
- package/registry/frameworks/hono/README.md +186 -0
- package/registry/frameworks/hono/index.html +22 -0
- package/registry/frameworks/hono/package.json +42 -0
- package/registry/frameworks/hono/src/components/Chat.tsx +124 -0
- package/registry/frameworks/hono/src/components/ChatApp.tsx +129 -0
- package/registry/frameworks/hono/src/components/Conversation.tsx +90 -0
- package/registry/frameworks/hono/src/components/MessageBubbles.tsx +88 -0
- package/registry/frameworks/hono/src/components/MessageReasoning.tsx +71 -0
- package/registry/frameworks/hono/src/components/MessageThread.tsx +135 -0
- package/registry/frameworks/hono/src/components/StreamingIndicator.tsx +36 -0
- package/registry/frameworks/hono/src/components/Subagents.tsx +120 -0
- package/registry/frameworks/hono/src/components/ThemeIcons.tsx +31 -0
- package/registry/frameworks/hono/src/components/ThreadHistory.tsx +73 -0
- package/registry/frameworks/hono/src/components/ToolCall.tsx +89 -0
- package/registry/frameworks/hono/src/lib/agent/types.ts +4 -0
- package/registry/frameworks/hono/src/lib/chat/threads-client.ts +57 -0
- package/registry/frameworks/hono/src/main.tsx +11 -0
- package/registry/frameworks/hono/src/styles/globals.css +714 -0
- package/registry/frameworks/hono/src/vite-env.d.ts +1 -0
- package/registry/frameworks/hono/tsconfig.app.json +22 -0
- package/registry/frameworks/hono/tsconfig.json +7 -0
- package/registry/frameworks/hono/tsconfig.worker.json +18 -0
- package/registry/frameworks/hono/vite.config.ts +16 -0
- package/registry/frameworks/hono/worker/agent/index.ts +53 -0
- package/registry/frameworks/hono/worker/agent/middleware.ts +20 -0
- package/registry/frameworks/hono/worker/agent/tools.ts +55 -0
- package/registry/frameworks/hono/worker/durable-objects/thread-session.ts +159 -0
- package/registry/frameworks/hono/worker/env.d.ts +17 -0
- package/registry/frameworks/hono/worker/index.ts +140 -0
- package/registry/frameworks/hono/worker/server/registry.ts +39 -0
- package/registry/frameworks/hono/worker/server/runs.ts +82 -0
- package/registry/frameworks/hono/worker/server/serialize.ts +30 -0
- package/registry/frameworks/hono/worker/server/threads.ts +404 -0
- package/registry/frameworks/hono/worker/tsconfig.json +4 -0
- package/registry/frameworks/hono/wrangler.jsonc +28 -0
- package/registry/frameworks/hono.ts +35 -0
- package/registry/frameworks/next/.env.example +6 -0
- package/registry/frameworks/next/README.md +173 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/commands/route.ts +21 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/history/route.ts +35 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/route.ts +13 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/state/route.ts +51 -0
- package/registry/frameworks/next/app/api/threads/[threadId]/stream/route.ts +30 -0
- package/registry/frameworks/next/app/api/threads/route.ts +11 -0
- package/registry/frameworks/next/app/favicon.ico +0 -0
- package/registry/frameworks/next/app/globals.css +712 -0
- package/registry/frameworks/next/app/layout.tsx +34 -0
- package/registry/frameworks/next/app/page.tsx +5 -0
- package/registry/frameworks/next/components/Chat.tsx +124 -0
- package/registry/frameworks/next/components/ChatApp.tsx +129 -0
- package/registry/frameworks/next/components/Conversation.tsx +90 -0
- package/registry/frameworks/next/components/MessageBubbles.tsx +88 -0
- package/registry/frameworks/next/components/MessageReasoning.tsx +71 -0
- package/registry/frameworks/next/components/MessageThread.tsx +135 -0
- package/registry/frameworks/next/components/StreamingIndicator.tsx +36 -0
- package/registry/frameworks/next/components/Subagents.tsx +120 -0
- package/registry/frameworks/next/components/ThemeIcons.tsx +31 -0
- package/registry/frameworks/next/components/ThreadHistory.tsx +73 -0
- package/registry/frameworks/next/components/ToolCall.tsx +89 -0
- package/registry/frameworks/next/eslint.config.mjs +18 -0
- package/registry/frameworks/next/lib/agent/index.ts +95 -0
- package/registry/frameworks/next/lib/agent/middleware.ts +40 -0
- package/registry/frameworks/next/lib/agent/tools.ts +66 -0
- package/registry/frameworks/next/lib/chat/threads-client.ts +57 -0
- package/registry/frameworks/next/lib/server/registry.ts +57 -0
- package/registry/frameworks/next/lib/server/serialize.ts +32 -0
- package/registry/frameworks/next/lib/server/session.ts +212 -0
- package/registry/frameworks/next/lib/server/threads.ts +406 -0
- package/registry/frameworks/next/next.config.ts +7 -0
- package/registry/frameworks/next/package.json +37 -0
- package/registry/frameworks/next/postcss.config.mjs +7 -0
- package/registry/frameworks/next/public/file.svg +1 -0
- package/registry/frameworks/next/public/globe.svg +1 -0
- package/registry/frameworks/next/public/next.svg +1 -0
- package/registry/frameworks/next/public/vercel.svg +1 -0
- package/registry/frameworks/next/public/window.svg +1 -0
- package/registry/frameworks/next/tsconfig.json +34 -0
- package/registry/frameworks/next.ts +17 -0
- package/registry/frameworks/nuxt/.env.example +3 -0
- package/registry/frameworks/nuxt/README.md +133 -0
- package/registry/frameworks/nuxt/app/app.vue +26 -0
- package/registry/frameworks/nuxt/app/assets/css/main.css +707 -0
- package/registry/frameworks/nuxt/app/components/Chat.vue +105 -0
- package/registry/frameworks/nuxt/app/components/ChatApp.vue +89 -0
- package/registry/frameworks/nuxt/app/components/ChatThread.vue +27 -0
- package/registry/frameworks/nuxt/app/components/MessageBubble.vue +60 -0
- package/registry/frameworks/nuxt/app/components/MessageBubbles.vue +213 -0
- package/registry/frameworks/nuxt/app/components/MessageList.vue +51 -0
- package/registry/frameworks/nuxt/app/components/MessageReasoning.vue +53 -0
- package/registry/frameworks/nuxt/app/components/StreamingIndicator.vue +9 -0
- package/registry/frameworks/nuxt/app/components/SubagentDetail.vue +51 -0
- package/registry/frameworks/nuxt/app/components/SubagentList.vue +49 -0
- package/registry/frameworks/nuxt/app/components/ThemeToggle.vue +43 -0
- package/registry/frameworks/nuxt/app/components/ThreadHistory.vue +65 -0
- package/registry/frameworks/nuxt/app/components/ToolCall.vue +81 -0
- package/registry/frameworks/nuxt/app/components/TypingDots.vue +14 -0
- package/registry/frameworks/nuxt/app/composables/useTheme.ts +14 -0
- package/registry/frameworks/nuxt/app/utils/streaming.ts +44 -0
- package/registry/frameworks/nuxt/app/utils/threads.ts +57 -0
- package/registry/frameworks/nuxt/nuxt.config.ts +6 -0
- package/registry/frameworks/nuxt/package.json +28 -0
- package/registry/frameworks/nuxt/public/favicon.ico +0 -0
- package/registry/frameworks/nuxt/public/robots.txt +2 -0
- package/registry/frameworks/nuxt/server/agent/index.ts +89 -0
- package/registry/frameworks/nuxt/server/agent/middleware.ts +38 -0
- package/registry/frameworks/nuxt/server/agent/tools.ts +66 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/commands.post.ts +16 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/history.post.ts +37 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/index.delete.ts +12 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/state.get.ts +22 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/state.post.ts +32 -0
- package/registry/frameworks/nuxt/server/api/threads/[threadId]/stream.post.ts +24 -0
- package/registry/frameworks/nuxt/server/api/threads/index.get.ts +13 -0
- package/registry/frameworks/nuxt/server/utils/runtime.ts +42 -0
- package/registry/frameworks/nuxt/server/utils/serialize.ts +30 -0
- package/registry/frameworks/nuxt/server/utils/session.ts +210 -0
- package/registry/frameworks/nuxt/server/utils/threads.ts +404 -0
- package/registry/frameworks/nuxt/tsconfig.json +18 -0
- package/registry/frameworks/nuxt.ts +17 -0
- package/registry/frameworks/vite/.env.example +20 -0
- package/registry/frameworks/vite/README.md +149 -0
- package/registry/frameworks/vite/agent/index.ts +59 -0
- package/registry/frameworks/vite/agent/middleware.ts +24 -0
- package/registry/frameworks/vite/agent/tools.ts +64 -0
- package/registry/frameworks/vite/index.html +23 -0
- package/registry/frameworks/vite/langgraph.json +16 -0
- package/registry/frameworks/vite/package.json +39 -0
- package/registry/frameworks/vite/public/favicon.ico +0 -0
- package/registry/frameworks/vite/scripts/vite-langgraph-proxy.ts +34 -0
- package/registry/frameworks/vite/src/components/Chat.tsx +124 -0
- package/registry/frameworks/vite/src/components/ChatApp.tsx +122 -0
- package/registry/frameworks/vite/src/components/Conversation.tsx +91 -0
- package/registry/frameworks/vite/src/components/MessageBubbles.tsx +88 -0
- package/registry/frameworks/vite/src/components/MessageReasoning.tsx +71 -0
- package/registry/frameworks/vite/src/components/MessageThread.tsx +135 -0
- package/registry/frameworks/vite/src/components/StreamingIndicator.tsx +36 -0
- package/registry/frameworks/vite/src/components/Subagents.tsx +120 -0
- package/registry/frameworks/vite/src/components/ThemeIcons.tsx +31 -0
- package/registry/frameworks/vite/src/components/ThreadHistory.tsx +73 -0
- package/registry/frameworks/vite/src/components/ToolCall.tsx +89 -0
- package/registry/frameworks/vite/src/lib/agent-type.ts +4 -0
- package/registry/frameworks/vite/src/lib/chat/threads-client.ts +114 -0
- package/registry/frameworks/vite/src/main.tsx +11 -0
- package/registry/frameworks/vite/src/styles/globals.css +714 -0
- package/registry/frameworks/vite/src/vite-env.d.ts +11 -0
- package/registry/frameworks/vite/tsconfig.app.json +24 -0
- package/registry/frameworks/vite/tsconfig.json +7 -0
- package/registry/frameworks/vite/tsconfig.node.json +21 -0
- package/registry/frameworks/vite/vercel.json +3 -0
- package/registry/frameworks/vite/vite.config.ts +24 -0
- package/registry/frameworks/vite.ts +17 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread state helpers backed by the graph checkpointer.
|
|
3
|
+
*
|
|
4
|
+
* Implements the LangGraph SDK thread state wire-shape consumed by
|
|
5
|
+
* `client.threads.getState` / `updateState` (`GET|POST /threads/:id/state`) and
|
|
6
|
+
* `getHistory` (`POST /threads/:id/history`), aligned with the Agent Protocol
|
|
7
|
+
* thread model.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { MemorySaver } from "@langchain/langgraph";
|
|
11
|
+
import type { CompiledGraphType } from "@langchain/langgraph";
|
|
12
|
+
import type { RunnableConfig } from "@langchain/core/runnables";
|
|
13
|
+
|
|
14
|
+
import { isRecord, sanitizeForJson } from "./serialize";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compiled LangGraph instance exposed through the custom protocol server.
|
|
18
|
+
*
|
|
19
|
+
* Thread routes read and write checkpointed state through this graph's
|
|
20
|
+
* checkpointer rather than maintaining a separate thread store.
|
|
21
|
+
*/
|
|
22
|
+
export type LocalProtocolGraph = CompiledGraphType;
|
|
23
|
+
|
|
24
|
+
type StateSnapshot = Awaited<ReturnType<LocalProtocolGraph["getState"]>>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Raised when a thread has no checkpoint yet.
|
|
28
|
+
*
|
|
29
|
+
* The route handlers map this to HTTP 404 so the LangGraph SDK can bootstrap
|
|
30
|
+
* the thread via `POST /threads/:id/state` before the first run.
|
|
31
|
+
*/
|
|
32
|
+
export class ThreadNotFoundError extends Error {
|
|
33
|
+
readonly threadId: string;
|
|
34
|
+
|
|
35
|
+
constructor(threadId: string) {
|
|
36
|
+
super(`Thread ${threadId} not found`);
|
|
37
|
+
this.name = "ThreadNotFoundError";
|
|
38
|
+
this.threadId = threadId;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Graph node used when bootstrapping an empty thread.
|
|
44
|
+
*
|
|
45
|
+
* Empty `messages` updates must land on `__start__` so conditional edges are
|
|
46
|
+
* not evaluated before the first human turn exists.
|
|
47
|
+
*/
|
|
48
|
+
const INITIAL_UPDATE_NODE = "__start__";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Default graph node for non-empty state updates on an existing checkpoint.
|
|
52
|
+
*
|
|
53
|
+
* Matches the agent's model node when the client omits `as_node`.
|
|
54
|
+
*/
|
|
55
|
+
const DEFAULT_UPDATE_NODE = "model_request";
|
|
56
|
+
|
|
57
|
+
/** Build the {@link RunnableConfig} that scopes graph calls to a thread id. */
|
|
58
|
+
function threadConfig(threadId: string): RunnableConfig {
|
|
59
|
+
return { configurable: { thread_id: threadId } };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build the config passed to `getStateHistory` for root or subgraph-scoped
|
|
64
|
+
* history, matching langgraph-api's `{ thread_id, checkpoint_ns: "", ...checkpoint }`.
|
|
65
|
+
*/
|
|
66
|
+
function historyConfig(
|
|
67
|
+
threadId: string,
|
|
68
|
+
checkpoint?: Record<string, unknown> | null,
|
|
69
|
+
): RunnableConfig {
|
|
70
|
+
const configurable: Record<string, unknown> = {
|
|
71
|
+
thread_id: threadId,
|
|
72
|
+
checkpoint_ns: "",
|
|
73
|
+
};
|
|
74
|
+
if (checkpoint && isRecord(checkpoint)) {
|
|
75
|
+
Object.assign(configurable, checkpoint);
|
|
76
|
+
}
|
|
77
|
+
return { configurable };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Read the `configurable` bag from a LangGraph run config. */
|
|
81
|
+
function configurableOf(config: RunnableConfig): Record<string, unknown> {
|
|
82
|
+
return isRecord(config.configurable) ? config.configurable : {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Return whether a {@link StateSnapshot} represents a persisted checkpoint.
|
|
87
|
+
*
|
|
88
|
+
* LangGraph returns an empty configurable bag before the first write; the SDK
|
|
89
|
+
* treats that as "thread not found" rather than an empty thread state.
|
|
90
|
+
*/
|
|
91
|
+
function threadHasCheckpoint(snapshot: StateSnapshot): boolean {
|
|
92
|
+
const checkpointId = configurableOf(snapshot.config).checkpoint_id;
|
|
93
|
+
return typeof checkpointId === "string" && checkpointId.length > 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isStateSnapshot(state: unknown): state is StateSnapshot {
|
|
97
|
+
return isRecord(state) && "values" in state && "next" in state;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function serializeTaskError(error: unknown): string | null {
|
|
101
|
+
if (error == null) return null;
|
|
102
|
+
if (error instanceof Error) return error.message;
|
|
103
|
+
if (typeof error === "string") return error;
|
|
104
|
+
return String(error);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Map a LangGraph run config to the SDK checkpoint wire shape. */
|
|
108
|
+
function runnableConfigToCheckpoint(
|
|
109
|
+
config: RunnableConfig | null | undefined,
|
|
110
|
+
fallbackThreadId?: string,
|
|
111
|
+
): Record<string, unknown> | null {
|
|
112
|
+
if (!config || !isRecord(config.configurable)) return null;
|
|
113
|
+
const c = config.configurable;
|
|
114
|
+
const thread_id =
|
|
115
|
+
typeof c.thread_id === "string" ? c.thread_id : fallbackThreadId;
|
|
116
|
+
if (!thread_id || typeof c.checkpoint_id !== "string") return null;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
thread_id,
|
|
120
|
+
checkpoint_id: c.checkpoint_id,
|
|
121
|
+
checkpoint_ns: typeof c.checkpoint_ns === "string" ? c.checkpoint_ns : "",
|
|
122
|
+
checkpoint_map: isRecord(c.checkpoint_map) ? c.checkpoint_map : null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function taskCheckpointFromState(
|
|
127
|
+
state: unknown,
|
|
128
|
+
): Record<string, unknown> | null {
|
|
129
|
+
if (state == null || !isRecord(state) || !isRecord(state.configurable)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const c = state.configurable;
|
|
133
|
+
if (typeof c.thread_id !== "string") return null;
|
|
134
|
+
return {
|
|
135
|
+
thread_id: c.thread_id,
|
|
136
|
+
checkpoint_id: typeof c.checkpoint_id === "string" ? c.checkpoint_id : null,
|
|
137
|
+
checkpoint_ns: typeof c.checkpoint_ns === "string" ? c.checkpoint_ns : "",
|
|
138
|
+
checkpoint_map: isRecord(c.checkpoint_map) ? c.checkpoint_map : null,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Serialize a LangGraph {@link StateSnapshot} to the SDK `ThreadState` shape.
|
|
144
|
+
*
|
|
145
|
+
* Aligned with langgraph-api's `stateSnapshotToThreadState`, plus
|
|
146
|
+
* {@link sanitizeForJson} on `values` and nested task `result` payloads.
|
|
147
|
+
*/
|
|
148
|
+
export function serializeThreadState(
|
|
149
|
+
snapshot: StateSnapshot,
|
|
150
|
+
threadId: string,
|
|
151
|
+
): Record<string, unknown> {
|
|
152
|
+
const configurable = configurableOf(snapshot.config);
|
|
153
|
+
const checkpoint = runnableConfigToCheckpoint(snapshot.config, threadId) ?? {
|
|
154
|
+
thread_id: threadId,
|
|
155
|
+
checkpoint_id:
|
|
156
|
+
typeof configurable.checkpoint_id === "string"
|
|
157
|
+
? configurable.checkpoint_id
|
|
158
|
+
: null,
|
|
159
|
+
checkpoint_ns:
|
|
160
|
+
typeof configurable.checkpoint_ns === "string"
|
|
161
|
+
? configurable.checkpoint_ns
|
|
162
|
+
: "",
|
|
163
|
+
checkpoint_map: null,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const tasks = (snapshot.tasks ?? []).map((task) => {
|
|
167
|
+
const record = task as {
|
|
168
|
+
id?: unknown;
|
|
169
|
+
name?: unknown;
|
|
170
|
+
error?: unknown;
|
|
171
|
+
interrupts?: unknown;
|
|
172
|
+
path?: unknown;
|
|
173
|
+
result?: unknown;
|
|
174
|
+
state?: unknown;
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
id: record.id,
|
|
178
|
+
name: record.name,
|
|
179
|
+
error: serializeTaskError(record.error),
|
|
180
|
+
interrupts: Array.isArray(record.interrupts) ? record.interrupts : [],
|
|
181
|
+
path: record.path ?? null,
|
|
182
|
+
checkpoint: taskCheckpointFromState(record.state),
|
|
183
|
+
state:
|
|
184
|
+
record.state != null && isStateSnapshot(record.state)
|
|
185
|
+
? serializeThreadState(record.state, threadId)
|
|
186
|
+
: null,
|
|
187
|
+
result: record.result != null ? sanitizeForJson(record.result) : null,
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const parentConfig = (snapshot as { parentConfig?: RunnableConfig })
|
|
192
|
+
.parentConfig;
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
values: sanitizeForJson(snapshot.values ?? {}),
|
|
196
|
+
next: [...(snapshot.next ?? [])],
|
|
197
|
+
tasks,
|
|
198
|
+
checkpoint,
|
|
199
|
+
metadata: { ...snapshot.metadata },
|
|
200
|
+
created_at: snapshot.createdAt ?? null,
|
|
201
|
+
parent_checkpoint: runnableConfigToCheckpoint(parentConfig, threadId),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Summary of a thread for the history sidebar. */
|
|
206
|
+
export type ThreadSummary = {
|
|
207
|
+
id: string;
|
|
208
|
+
title: string;
|
|
209
|
+
updatedAt: string | null;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const UNTITLED = "New conversation";
|
|
213
|
+
|
|
214
|
+
/** Derive a sidebar title from the first human message in a thread. */
|
|
215
|
+
function deriveTitle(values: unknown): string {
|
|
216
|
+
if (!isRecord(values) || !Array.isArray(values.messages)) return UNTITLED;
|
|
217
|
+
for (const message of values.messages) {
|
|
218
|
+
if (!isRecord(message) || message.type !== "human") continue;
|
|
219
|
+
const { content } = message;
|
|
220
|
+
const text =
|
|
221
|
+
typeof content === "string"
|
|
222
|
+
? content
|
|
223
|
+
: Array.isArray(content)
|
|
224
|
+
? content
|
|
225
|
+
.map((block) =>
|
|
226
|
+
isRecord(block) && typeof block.text === "string"
|
|
227
|
+
? block.text
|
|
228
|
+
: "",
|
|
229
|
+
)
|
|
230
|
+
.join("")
|
|
231
|
+
: "";
|
|
232
|
+
const trimmed = text.trim();
|
|
233
|
+
if (trimmed) return trimmed.slice(0, 80);
|
|
234
|
+
}
|
|
235
|
+
return UNTITLED;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* List every thread known to the checkpointer, newest first.
|
|
240
|
+
*
|
|
241
|
+
* The checkpointer is the single source of truth: thread ids are the top-level
|
|
242
|
+
* keys of {@link MemorySaver.storage}, and each thread's title/timestamp is
|
|
243
|
+
* derived from its latest checkpoint. Restarting the server clears all of this.
|
|
244
|
+
*/
|
|
245
|
+
export async function listThreads(
|
|
246
|
+
graph: LocalProtocolGraph,
|
|
247
|
+
checkpointer: MemorySaver,
|
|
248
|
+
): Promise<ThreadSummary[]> {
|
|
249
|
+
const ids = Object.keys(checkpointer.storage);
|
|
250
|
+
const summaries: ThreadSummary[] = [];
|
|
251
|
+
for (const id of ids) {
|
|
252
|
+
try {
|
|
253
|
+
const state = await getThreadState(graph, id);
|
|
254
|
+
summaries.push({
|
|
255
|
+
id,
|
|
256
|
+
title: deriveTitle(state.values),
|
|
257
|
+
updatedAt:
|
|
258
|
+
typeof state.created_at === "string" ? state.created_at : null,
|
|
259
|
+
});
|
|
260
|
+
} catch {
|
|
261
|
+
// Skip threads without a readable checkpoint.
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
summaries.sort((a, b) =>
|
|
265
|
+
(b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""),
|
|
266
|
+
);
|
|
267
|
+
return summaries;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Read checkpointed thread state for `GET /threads/:threadId/state`.
|
|
272
|
+
*
|
|
273
|
+
* @throws {@link ThreadNotFoundError} When the thread has no checkpoint yet.
|
|
274
|
+
*/
|
|
275
|
+
export async function getThreadState(
|
|
276
|
+
graph: LocalProtocolGraph,
|
|
277
|
+
threadId: string,
|
|
278
|
+
): Promise<Record<string, unknown>> {
|
|
279
|
+
const snapshot = await graph.getState(threadConfig(threadId));
|
|
280
|
+
if (!threadHasCheckpoint(snapshot)) throw new ThreadNotFoundError(threadId);
|
|
281
|
+
return serializeThreadState(snapshot, threadId);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Parse the `before` pagination cursor accepted by `POST /threads/:id/history`.
|
|
286
|
+
*/
|
|
287
|
+
function parseBeforeCursor(
|
|
288
|
+
threadId: string,
|
|
289
|
+
before: unknown,
|
|
290
|
+
): RunnableConfig | undefined {
|
|
291
|
+
if (before == null) return undefined;
|
|
292
|
+
if (typeof before === "string") {
|
|
293
|
+
return { configurable: { thread_id: threadId, checkpoint_id: before } };
|
|
294
|
+
}
|
|
295
|
+
if (!isRecord(before)) return undefined;
|
|
296
|
+
|
|
297
|
+
if (isRecord(before.configurable)) {
|
|
298
|
+
return {
|
|
299
|
+
configurable: { thread_id: threadId, ...before.configurable },
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const checkpointId = before.checkpoint_id;
|
|
304
|
+
if (typeof checkpointId !== "string") return undefined;
|
|
305
|
+
|
|
306
|
+
const cursor: RunnableConfig = {
|
|
307
|
+
configurable: { thread_id: threadId, checkpoint_id: checkpointId },
|
|
308
|
+
};
|
|
309
|
+
if (typeof before.checkpoint_ns === "string") {
|
|
310
|
+
(cursor.configurable as Record<string, unknown>).checkpoint_ns =
|
|
311
|
+
before.checkpoint_ns;
|
|
312
|
+
}
|
|
313
|
+
return cursor;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* List past thread states for `POST /threads/:threadId/history`.
|
|
318
|
+
*
|
|
319
|
+
* @throws {@link ThreadNotFoundError} When the thread has no checkpoint yet.
|
|
320
|
+
*/
|
|
321
|
+
export async function getThreadHistory(
|
|
322
|
+
graph: LocalProtocolGraph,
|
|
323
|
+
threadId: string,
|
|
324
|
+
options: {
|
|
325
|
+
limit?: number;
|
|
326
|
+
before?: unknown;
|
|
327
|
+
metadata?: Record<string, unknown>;
|
|
328
|
+
checkpoint?: Record<string, unknown> | null;
|
|
329
|
+
} = {},
|
|
330
|
+
): Promise<Record<string, unknown>[]> {
|
|
331
|
+
await getThreadState(graph, threadId);
|
|
332
|
+
|
|
333
|
+
const history: Record<string, unknown>[] = [];
|
|
334
|
+
const iterator = graph.getStateHistory(
|
|
335
|
+
historyConfig(threadId, options.checkpoint),
|
|
336
|
+
{
|
|
337
|
+
before: parseBeforeCursor(threadId, options.before),
|
|
338
|
+
limit: options.limit ?? 10,
|
|
339
|
+
...(options.metadata ? { filter: options.metadata } : {}),
|
|
340
|
+
},
|
|
341
|
+
);
|
|
342
|
+
for await (const snapshot of iterator) {
|
|
343
|
+
history.push(serializeThreadState(snapshot, threadId));
|
|
344
|
+
}
|
|
345
|
+
return history;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Choose which graph node should receive an `updateState` write. */
|
|
349
|
+
function resolveUpdateNode(options: {
|
|
350
|
+
asNode?: string;
|
|
351
|
+
values: Record<string, unknown> | null;
|
|
352
|
+
hasCheckpoint: boolean;
|
|
353
|
+
}): string {
|
|
354
|
+
if (options.asNode) return options.asNode;
|
|
355
|
+
const messages = options.values?.messages;
|
|
356
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
357
|
+
return INITIAL_UPDATE_NODE;
|
|
358
|
+
}
|
|
359
|
+
if (!options.hasCheckpoint) return INITIAL_UPDATE_NODE;
|
|
360
|
+
return DEFAULT_UPDATE_NODE;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Create or update thread state for `POST /threads/:threadId/state`.
|
|
365
|
+
*
|
|
366
|
+
* Used by the browser bootstrap and by the SDK when hydrating or editing
|
|
367
|
+
* conversation history. Applies the update at {@link resolveUpdateNode}, then
|
|
368
|
+
* returns the latest serialized snapshot.
|
|
369
|
+
*/
|
|
370
|
+
export async function updateThreadState(
|
|
371
|
+
graph: LocalProtocolGraph,
|
|
372
|
+
threadId: string,
|
|
373
|
+
options: {
|
|
374
|
+
values?: Record<string, unknown> | null;
|
|
375
|
+
checkpoint?: Record<string, unknown> | null;
|
|
376
|
+
asNode?: string;
|
|
377
|
+
} = {},
|
|
378
|
+
): Promise<Record<string, unknown>> {
|
|
379
|
+
let config = threadConfig(threadId);
|
|
380
|
+
const checkpoint = options.checkpoint;
|
|
381
|
+
if (checkpoint && typeof checkpoint.checkpoint_id === "string") {
|
|
382
|
+
config = {
|
|
383
|
+
configurable: {
|
|
384
|
+
...configurableOf(config),
|
|
385
|
+
checkpoint_id: checkpoint.checkpoint_id,
|
|
386
|
+
...(typeof checkpoint.checkpoint_ns === "string"
|
|
387
|
+
? { checkpoint_ns: checkpoint.checkpoint_ns }
|
|
388
|
+
: {}),
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const snapshot = await graph.getState(config);
|
|
394
|
+
const resolvedValues = options.values ?? { messages: [] };
|
|
395
|
+
const resolvedAsNode = resolveUpdateNode({
|
|
396
|
+
asNode: options.asNode,
|
|
397
|
+
values: resolvedValues,
|
|
398
|
+
hasCheckpoint: threadHasCheckpoint(snapshot),
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
await graph.updateState(config, resolvedValues, resolvedAsNode);
|
|
402
|
+
const updated = await graph.getState(threadConfig(threadId));
|
|
403
|
+
return serializeThreadState(updated, threadId);
|
|
404
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "js-cloudflare",
|
|
4
|
+
"main": "./worker/index.ts",
|
|
5
|
+
"compatibility_date": "2026-06-15",
|
|
6
|
+
"compatibility_flags": [
|
|
7
|
+
"nodejs_compat",
|
|
8
|
+
"nodejs_compat_populate_process_env",
|
|
9
|
+
],
|
|
10
|
+
"assets": {
|
|
11
|
+
"not_found_handling": "single-page-application",
|
|
12
|
+
"run_worker_first": ["/api/*"],
|
|
13
|
+
},
|
|
14
|
+
"durable_objects": {
|
|
15
|
+
"bindings": [
|
|
16
|
+
{
|
|
17
|
+
"name": "SESSIONS",
|
|
18
|
+
"class_name": "ThreadSession",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
"migrations": [
|
|
23
|
+
{
|
|
24
|
+
"tag": "v1",
|
|
25
|
+
"new_sqlite_classes": ["ThreadSession"],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createFramework } from "../../src/registry/framework.js";
|
|
2
|
+
import type { ProviderAwareFile } from "../../src/registry/provider.js";
|
|
3
|
+
|
|
4
|
+
const ENV_D_TS: ProviderAwareFile = {
|
|
5
|
+
path: "worker/env.d.ts",
|
|
6
|
+
getContent: ({ providerConfig }) => {
|
|
7
|
+
const envVars = providerConfig.env
|
|
8
|
+
.map((e) => ` ${e.name}: string;`)
|
|
9
|
+
.join("\n");
|
|
10
|
+
|
|
11
|
+
return `interface Env {
|
|
12
|
+
ASSETS: Fetcher;
|
|
13
|
+
SESSIONS: DurableObjectNamespace;
|
|
14
|
+
${envVars}
|
|
15
|
+
[key: string]: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const hono = createFramework({
|
|
22
|
+
id: "hono",
|
|
23
|
+
title: "Hono",
|
|
24
|
+
defaultProjectName: "hono-deepagents",
|
|
25
|
+
address: {
|
|
26
|
+
scheme: "github",
|
|
27
|
+
owner: "aolsenjazz",
|
|
28
|
+
repo: "deployment-cookbook",
|
|
29
|
+
subPath: "js-cloudflare",
|
|
30
|
+
},
|
|
31
|
+
envFilePath: ".env",
|
|
32
|
+
packageJsonPath: "package.json",
|
|
33
|
+
agentPath: "worker/agent",
|
|
34
|
+
files: [ENV_D_TS],
|
|
35
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Deploying a LangChain Agent with Next.js
|
|
2
|
+
|
|
3
|
+
An example app that deploys a LangChain **deep agent** entirely inside a Next.js
|
|
4
|
+
App Router project — streaming chat UI, subagents, and thread history, all backed
|
|
5
|
+
by the [Agent Streaming Protocol](https://github.com/langchain-ai/agent-protocol/tree/main/streaming) implemented as
|
|
6
|
+
Next.js Route Handlers (HTTP + SSE). No separate backend process.
|
|
7
|
+
|
|
8
|
+
## Deploy to Vercel
|
|
9
|
+
|
|
10
|
+
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flangchain-ai%2Fdeployment-cookbook&root-directory=js-next&env=OPENAI_API_KEY&envDescription=OpenAI%20API%20key%20for%20the%20agent%20and%20its%20subagents)
|
|
11
|
+
|
|
12
|
+
1. Click **Deploy with Vercel** (or import
|
|
13
|
+
[`langchain-ai/deployment-cookbook`](https://github.com/langchain-ai/deployment-cookbook)
|
|
14
|
+
manually).
|
|
15
|
+
2. Set **Root Directory** to `js-next`.
|
|
16
|
+
3. Add `OPENAI_API_KEY` in project settings.
|
|
17
|
+
4. Deploy.
|
|
18
|
+
|
|
19
|
+
That is all that is required for a first deploy. The route handlers already set
|
|
20
|
+
`runtime = "nodejs"` and the SSE route sets `dynamic = "force-dynamic"`, which
|
|
21
|
+
Vercel needs for streaming.
|
|
22
|
+
|
|
23
|
+
Optionally enable LangSmith tracing by adding the variables from
|
|
24
|
+
[`.env.example`](./.env.example).
|
|
25
|
+
|
|
26
|
+
## Required API endpoints
|
|
27
|
+
|
|
28
|
+
The app exposes the Agent Streaming Protocol under `/api/threads/...`. Route
|
|
29
|
+
handlers live in `app/api/threads/`.
|
|
30
|
+
|
|
31
|
+
### Minimum (streaming chat)
|
|
32
|
+
|
|
33
|
+
These three endpoints are enough to run a single-threaded streaming chat with
|
|
34
|
+
`@langchain/react`'s `HttpAgentServerAdapter`:
|
|
35
|
+
|
|
36
|
+
| Method | Path | Purpose |
|
|
37
|
+
| -------------- | --------------------------------- | -------------------------------------------------------------- |
|
|
38
|
+
| `POST` | `/api/threads/:threadId/commands` | Accept protocol commands (`run.start`, …) and start agent runs |
|
|
39
|
+
| `POST` | `/api/threads/:threadId/stream` | SSE stream of protocol events for a run |
|
|
40
|
+
| `GET` / `POST` | `/api/threads/:threadId/state` | Read and bootstrap checkpointed thread state |
|
|
41
|
+
|
|
42
|
+
The client bootstraps a thread with `GET /state` (and `POST /state` on 404) so
|
|
43
|
+
hydration does not 404 before the first message is sent.
|
|
44
|
+
|
|
45
|
+
### Optional (this app's sidebar)
|
|
46
|
+
|
|
47
|
+
This example also implements endpoints for the thread-history sidebar. You can
|
|
48
|
+
omit them if your UI does not need multi-thread management:
|
|
49
|
+
|
|
50
|
+
| Method | Path | Purpose |
|
|
51
|
+
| -------- | -------------------------------- | --------------------------------------------- |
|
|
52
|
+
| `GET` | `/api/threads` | List threads known to the checkpointer |
|
|
53
|
+
| `DELETE` | `/api/threads/:threadId` | Delete a thread's session and checkpoints |
|
|
54
|
+
| `POST` | `/api/threads/:threadId/history` | Paginated checkpoint history (Agent Protocol) |
|
|
55
|
+
|
|
56
|
+
### Request flow
|
|
57
|
+
|
|
58
|
+
```mermaid
|
|
59
|
+
flowchart TB
|
|
60
|
+
subgraph browser["Browser"]
|
|
61
|
+
SP["StreamProvider"]
|
|
62
|
+
HAA["HttpAgentServerAdapter"]
|
|
63
|
+
SP --- HAA
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
subgraph routes["Next.js Route Handlers (Node runtime)"]
|
|
67
|
+
CMD["POST /api/threads/:id/commands"]
|
|
68
|
+
STR["POST /api/threads/:id/stream (SSE)"]
|
|
69
|
+
STA["GET|POST /api/threads/:id/state"]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
subgraph server["lib/server"]
|
|
73
|
+
SRV["session · threads · registry"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
subgraph agent["lib/agent"]
|
|
77
|
+
AGT["createDeepAgent + checkpointer"]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
HAA -->|POST| CMD
|
|
81
|
+
HAA -->|POST| STR
|
|
82
|
+
HAA -->|GET / POST| STA
|
|
83
|
+
CMD --> SRV
|
|
84
|
+
STR --> SRV
|
|
85
|
+
STA --> SRV
|
|
86
|
+
SRV --> AGT
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
1. Bootstrap thread state (`GET`/`POST /state`).
|
|
90
|
+
2. On submit, the SDK sends `run.start` to `/commands` and receives a `run_id`.
|
|
91
|
+
3. The SDK subscribes to `/stream` (SSE) for replay + live protocol events.
|
|
92
|
+
4. Subagent (`task`) runs emit namespaced events surfaced as `stream.subagents`.
|
|
93
|
+
|
|
94
|
+
## Production persistence
|
|
95
|
+
|
|
96
|
+
Out of the box, the agent uses an in-memory `MemorySaver` checkpointer
|
|
97
|
+
(`lib/agent/index.ts`) and a process-local session map (`lib/server/registry.ts`).
|
|
98
|
+
That works for local dev and single-instance servers, but on Vercel (serverless,
|
|
99
|
+
multiple replicas) conversation state is **not durable** across cold starts or
|
|
100
|
+
instances.
|
|
101
|
+
|
|
102
|
+
For production, swap in a [durable checkpointer](https://docs.langchain.com/oss/javascript/langgraph/checkpointers#checkpointer-libraries):
|
|
103
|
+
|
|
104
|
+
| Package | Backend |
|
|
105
|
+
| ------------------------------------------ | -------------------------- |
|
|
106
|
+
| `@langchain/langgraph-checkpoint-redis` | Redis (`RedisSaver`) |
|
|
107
|
+
| `@langchain/langgraph-checkpoint-postgres` | Postgres (`PostgresSaver`) |
|
|
108
|
+
| `@langchain/langgraph-checkpoint-sqlite` | SQLite (`SqliteSaver`) |
|
|
109
|
+
|
|
110
|
+
Replace `MemorySaver` in `lib/agent/index.ts` and pass the new checkpointer to
|
|
111
|
+
`createDeepAgent`. The route handlers and `lib/server/threads.ts` helpers stay
|
|
112
|
+
the same.
|
|
113
|
+
|
|
114
|
+
### Redis on Vercel
|
|
115
|
+
|
|
116
|
+
A common choice for Vercel is Redis via the
|
|
117
|
+
[Marketplace](https://vercel.com/docs/redis) (for example
|
|
118
|
+
[Upstash Redis](https://vercel.com/marketplace/upstash)). Install the
|
|
119
|
+
integration on your Vercel project; credentials are injected as environment
|
|
120
|
+
variables automatically.
|
|
121
|
+
|
|
122
|
+
Then wire `@langchain/langgraph-checkpoint-redis`:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { RedisSaver } from "@langchain/langgraph-checkpoint-redis";
|
|
126
|
+
|
|
127
|
+
const checkpointer = await RedisSaver.fromUrl(process.env.REDIS_URL!);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Use the connection string your Redis provider exposes (Upstash provides both
|
|
131
|
+
REST and Redis-protocol URLs — the checkpointer needs the Redis URL).
|
|
132
|
+
|
|
133
|
+
You will also want a shared session/replay store in `lib/server/registry.ts` so
|
|
134
|
+
SSE reconnection works across serverless invocations. The checkpointer swap is
|
|
135
|
+
the main step for durable thread history; the session store is a separate
|
|
136
|
+
concern for live-run replay.
|
|
137
|
+
|
|
138
|
+
See also: [checkpointer libraries](https://docs.langchain.com/oss/javascript/langgraph/checkpointers#checkpointer-libraries),
|
|
139
|
+
[add memory / persistence](https://docs.langchain.com/oss/javascript/langgraph/add-memory).
|
|
140
|
+
|
|
141
|
+
## Local development
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
cp .env.example .env.local # set OPENAI_API_KEY
|
|
145
|
+
pnpm install
|
|
146
|
+
pnpm dev
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Open [http://localhost:3000](http://localhost:3000).
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pnpm build # production build
|
|
153
|
+
pnpm start # serve the production build
|
|
154
|
+
pnpm lint # eslint
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Project layout
|
|
158
|
+
|
|
159
|
+
- `lib/agent/` — deep agent (`createDeepAgent`) with `researcher` and `math-whiz`
|
|
160
|
+
subagents and mock tools. Marked `server-only`.
|
|
161
|
+
- `lib/server/` — protocol server logic: `session.ts` (SSE runs),
|
|
162
|
+
`threads.ts` (checkpointer-backed state), `serialize.ts`, `registry.ts`.
|
|
163
|
+
- `app/api/threads/` — Route Handlers for the protocol endpoints above.
|
|
164
|
+
- `lib/chat/threads-client.ts` — browser thread bootstrap and sidebar helpers.
|
|
165
|
+
- `components/` — chat UI (`ChatApp`, `Chat`, `MessageList`, `Subagents`,
|
|
166
|
+
`ThreadHistory`, …).
|
|
167
|
+
|
|
168
|
+
## References
|
|
169
|
+
|
|
170
|
+
- [Agent Streaming Protocol](https://github.com/langchain-ai/agent-protocol/tree/main/streaming) — protocol spec consumed by `@langchain/react`'s `HttpAgentServerAdapter`
|
|
171
|
+
- [`react-custom-backend`](https://github.com/langchain-ai/streaming-cookbook) — original Vite + Hono reference for a custom protocol server
|
|
172
|
+
- [Next.js Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) — API routes used for the protocol endpoints
|
|
173
|
+
- [`deepagents`](https://www.npmjs.com/package/deepagents) — coordinator + subagent orchestration used by this demo
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Command } from "@langchain/protocol";
|
|
2
|
+
|
|
3
|
+
import { getSession } from "@/lib/server/registry";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
export const runtime = "nodejs";
|
|
7
|
+
|
|
8
|
+
type Params = { params: Promise<{ threadId: string }> };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `POST /api/threads/:threadId/commands`
|
|
12
|
+
*
|
|
13
|
+
* The request body is an Agent Protocol {@link Command}. The response is the
|
|
14
|
+
* command result emitted by the owning `LocalThreadSession`.
|
|
15
|
+
*/
|
|
16
|
+
export async function POST(request: Request, { params }: Params) {
|
|
17
|
+
const { threadId } = await params;
|
|
18
|
+
const command = (await request.json()) as Command;
|
|
19
|
+
const result = await getSession(threadId).handleCommand(command);
|
|
20
|
+
return Response.json(result);
|
|
21
|
+
}
|