@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.
Files changed (193) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +661 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +59 -0
  5. package/registry/frameworks/deno/.env.example +6 -0
  6. package/registry/frameworks/deno/README.md +137 -0
  7. package/registry/frameworks/deno/client/index.html +23 -0
  8. package/registry/frameworks/deno/client/package.json +30 -0
  9. package/registry/frameworks/deno/client/public/favicon.ico +0 -0
  10. package/registry/frameworks/deno/client/src/components/Chat.tsx +124 -0
  11. package/registry/frameworks/deno/client/src/components/ChatApp.tsx +129 -0
  12. package/registry/frameworks/deno/client/src/components/Conversation.tsx +91 -0
  13. package/registry/frameworks/deno/client/src/components/MessageBubbles.tsx +88 -0
  14. package/registry/frameworks/deno/client/src/components/MessageReasoning.tsx +71 -0
  15. package/registry/frameworks/deno/client/src/components/MessageThread.tsx +135 -0
  16. package/registry/frameworks/deno/client/src/components/StreamingIndicator.tsx +36 -0
  17. package/registry/frameworks/deno/client/src/components/Subagents.tsx +120 -0
  18. package/registry/frameworks/deno/client/src/components/ThemeIcons.tsx +31 -0
  19. package/registry/frameworks/deno/client/src/components/ThreadHistory.tsx +73 -0
  20. package/registry/frameworks/deno/client/src/components/ToolCall.tsx +89 -0
  21. package/registry/frameworks/deno/client/src/lib/agent-type.ts +4 -0
  22. package/registry/frameworks/deno/client/src/lib/chat/threads-client.ts +51 -0
  23. package/registry/frameworks/deno/client/src/main.tsx +11 -0
  24. package/registry/frameworks/deno/client/src/styles/globals.css +714 -0
  25. package/registry/frameworks/deno/client/src/vite-env.d.ts +1 -0
  26. package/registry/frameworks/deno/client/tsconfig.app.json +7 -0
  27. package/registry/frameworks/deno/client/tsconfig.json +24 -0
  28. package/registry/frameworks/deno/client/tsconfig.node.json +19 -0
  29. package/registry/frameworks/deno/client/vite.config.ts +24 -0
  30. package/registry/frameworks/deno/deno.json +16 -0
  31. package/registry/frameworks/deno/main.ts +23 -0
  32. package/registry/frameworks/deno/package.json +14 -0
  33. package/registry/frameworks/deno/server/agent/index.ts +60 -0
  34. package/registry/frameworks/deno/server/agent/middleware.ts +24 -0
  35. package/registry/frameworks/deno/server/agent/tools.ts +64 -0
  36. package/registry/frameworks/deno/server/registry.ts +40 -0
  37. package/registry/frameworks/deno/server/routes.ts +114 -0
  38. package/registry/frameworks/deno/server/serialize.ts +30 -0
  39. package/registry/frameworks/deno/server/session.ts +210 -0
  40. package/registry/frameworks/deno/server/threads.ts +404 -0
  41. package/registry/frameworks/deno.ts +17 -0
  42. package/registry/frameworks/hono/.env.example +6 -0
  43. package/registry/frameworks/hono/README.md +186 -0
  44. package/registry/frameworks/hono/index.html +22 -0
  45. package/registry/frameworks/hono/package.json +42 -0
  46. package/registry/frameworks/hono/src/components/Chat.tsx +124 -0
  47. package/registry/frameworks/hono/src/components/ChatApp.tsx +129 -0
  48. package/registry/frameworks/hono/src/components/Conversation.tsx +90 -0
  49. package/registry/frameworks/hono/src/components/MessageBubbles.tsx +88 -0
  50. package/registry/frameworks/hono/src/components/MessageReasoning.tsx +71 -0
  51. package/registry/frameworks/hono/src/components/MessageThread.tsx +135 -0
  52. package/registry/frameworks/hono/src/components/StreamingIndicator.tsx +36 -0
  53. package/registry/frameworks/hono/src/components/Subagents.tsx +120 -0
  54. package/registry/frameworks/hono/src/components/ThemeIcons.tsx +31 -0
  55. package/registry/frameworks/hono/src/components/ThreadHistory.tsx +73 -0
  56. package/registry/frameworks/hono/src/components/ToolCall.tsx +89 -0
  57. package/registry/frameworks/hono/src/lib/agent/types.ts +4 -0
  58. package/registry/frameworks/hono/src/lib/chat/threads-client.ts +57 -0
  59. package/registry/frameworks/hono/src/main.tsx +11 -0
  60. package/registry/frameworks/hono/src/styles/globals.css +714 -0
  61. package/registry/frameworks/hono/src/vite-env.d.ts +1 -0
  62. package/registry/frameworks/hono/tsconfig.app.json +22 -0
  63. package/registry/frameworks/hono/tsconfig.json +7 -0
  64. package/registry/frameworks/hono/tsconfig.worker.json +18 -0
  65. package/registry/frameworks/hono/vite.config.ts +16 -0
  66. package/registry/frameworks/hono/worker/agent/index.ts +53 -0
  67. package/registry/frameworks/hono/worker/agent/middleware.ts +20 -0
  68. package/registry/frameworks/hono/worker/agent/tools.ts +55 -0
  69. package/registry/frameworks/hono/worker/durable-objects/thread-session.ts +159 -0
  70. package/registry/frameworks/hono/worker/env.d.ts +17 -0
  71. package/registry/frameworks/hono/worker/index.ts +140 -0
  72. package/registry/frameworks/hono/worker/server/registry.ts +39 -0
  73. package/registry/frameworks/hono/worker/server/runs.ts +82 -0
  74. package/registry/frameworks/hono/worker/server/serialize.ts +30 -0
  75. package/registry/frameworks/hono/worker/server/threads.ts +404 -0
  76. package/registry/frameworks/hono/worker/tsconfig.json +4 -0
  77. package/registry/frameworks/hono/wrangler.jsonc +28 -0
  78. package/registry/frameworks/hono.ts +35 -0
  79. package/registry/frameworks/next/.env.example +6 -0
  80. package/registry/frameworks/next/README.md +173 -0
  81. package/registry/frameworks/next/app/api/threads/[threadId]/commands/route.ts +21 -0
  82. package/registry/frameworks/next/app/api/threads/[threadId]/history/route.ts +35 -0
  83. package/registry/frameworks/next/app/api/threads/[threadId]/route.ts +13 -0
  84. package/registry/frameworks/next/app/api/threads/[threadId]/state/route.ts +51 -0
  85. package/registry/frameworks/next/app/api/threads/[threadId]/stream/route.ts +30 -0
  86. package/registry/frameworks/next/app/api/threads/route.ts +11 -0
  87. package/registry/frameworks/next/app/favicon.ico +0 -0
  88. package/registry/frameworks/next/app/globals.css +712 -0
  89. package/registry/frameworks/next/app/layout.tsx +34 -0
  90. package/registry/frameworks/next/app/page.tsx +5 -0
  91. package/registry/frameworks/next/components/Chat.tsx +124 -0
  92. package/registry/frameworks/next/components/ChatApp.tsx +129 -0
  93. package/registry/frameworks/next/components/Conversation.tsx +90 -0
  94. package/registry/frameworks/next/components/MessageBubbles.tsx +88 -0
  95. package/registry/frameworks/next/components/MessageReasoning.tsx +71 -0
  96. package/registry/frameworks/next/components/MessageThread.tsx +135 -0
  97. package/registry/frameworks/next/components/StreamingIndicator.tsx +36 -0
  98. package/registry/frameworks/next/components/Subagents.tsx +120 -0
  99. package/registry/frameworks/next/components/ThemeIcons.tsx +31 -0
  100. package/registry/frameworks/next/components/ThreadHistory.tsx +73 -0
  101. package/registry/frameworks/next/components/ToolCall.tsx +89 -0
  102. package/registry/frameworks/next/eslint.config.mjs +18 -0
  103. package/registry/frameworks/next/lib/agent/index.ts +95 -0
  104. package/registry/frameworks/next/lib/agent/middleware.ts +40 -0
  105. package/registry/frameworks/next/lib/agent/tools.ts +66 -0
  106. package/registry/frameworks/next/lib/chat/threads-client.ts +57 -0
  107. package/registry/frameworks/next/lib/server/registry.ts +57 -0
  108. package/registry/frameworks/next/lib/server/serialize.ts +32 -0
  109. package/registry/frameworks/next/lib/server/session.ts +212 -0
  110. package/registry/frameworks/next/lib/server/threads.ts +406 -0
  111. package/registry/frameworks/next/next.config.ts +7 -0
  112. package/registry/frameworks/next/package.json +37 -0
  113. package/registry/frameworks/next/postcss.config.mjs +7 -0
  114. package/registry/frameworks/next/public/file.svg +1 -0
  115. package/registry/frameworks/next/public/globe.svg +1 -0
  116. package/registry/frameworks/next/public/next.svg +1 -0
  117. package/registry/frameworks/next/public/vercel.svg +1 -0
  118. package/registry/frameworks/next/public/window.svg +1 -0
  119. package/registry/frameworks/next/tsconfig.json +34 -0
  120. package/registry/frameworks/next.ts +17 -0
  121. package/registry/frameworks/nuxt/.env.example +3 -0
  122. package/registry/frameworks/nuxt/README.md +133 -0
  123. package/registry/frameworks/nuxt/app/app.vue +26 -0
  124. package/registry/frameworks/nuxt/app/assets/css/main.css +707 -0
  125. package/registry/frameworks/nuxt/app/components/Chat.vue +105 -0
  126. package/registry/frameworks/nuxt/app/components/ChatApp.vue +89 -0
  127. package/registry/frameworks/nuxt/app/components/ChatThread.vue +27 -0
  128. package/registry/frameworks/nuxt/app/components/MessageBubble.vue +60 -0
  129. package/registry/frameworks/nuxt/app/components/MessageBubbles.vue +213 -0
  130. package/registry/frameworks/nuxt/app/components/MessageList.vue +51 -0
  131. package/registry/frameworks/nuxt/app/components/MessageReasoning.vue +53 -0
  132. package/registry/frameworks/nuxt/app/components/StreamingIndicator.vue +9 -0
  133. package/registry/frameworks/nuxt/app/components/SubagentDetail.vue +51 -0
  134. package/registry/frameworks/nuxt/app/components/SubagentList.vue +49 -0
  135. package/registry/frameworks/nuxt/app/components/ThemeToggle.vue +43 -0
  136. package/registry/frameworks/nuxt/app/components/ThreadHistory.vue +65 -0
  137. package/registry/frameworks/nuxt/app/components/ToolCall.vue +81 -0
  138. package/registry/frameworks/nuxt/app/components/TypingDots.vue +14 -0
  139. package/registry/frameworks/nuxt/app/composables/useTheme.ts +14 -0
  140. package/registry/frameworks/nuxt/app/utils/streaming.ts +44 -0
  141. package/registry/frameworks/nuxt/app/utils/threads.ts +57 -0
  142. package/registry/frameworks/nuxt/nuxt.config.ts +6 -0
  143. package/registry/frameworks/nuxt/package.json +28 -0
  144. package/registry/frameworks/nuxt/public/favicon.ico +0 -0
  145. package/registry/frameworks/nuxt/public/robots.txt +2 -0
  146. package/registry/frameworks/nuxt/server/agent/index.ts +89 -0
  147. package/registry/frameworks/nuxt/server/agent/middleware.ts +38 -0
  148. package/registry/frameworks/nuxt/server/agent/tools.ts +66 -0
  149. package/registry/frameworks/nuxt/server/api/threads/[threadId]/commands.post.ts +16 -0
  150. package/registry/frameworks/nuxt/server/api/threads/[threadId]/history.post.ts +37 -0
  151. package/registry/frameworks/nuxt/server/api/threads/[threadId]/index.delete.ts +12 -0
  152. package/registry/frameworks/nuxt/server/api/threads/[threadId]/state.get.ts +22 -0
  153. package/registry/frameworks/nuxt/server/api/threads/[threadId]/state.post.ts +32 -0
  154. package/registry/frameworks/nuxt/server/api/threads/[threadId]/stream.post.ts +24 -0
  155. package/registry/frameworks/nuxt/server/api/threads/index.get.ts +13 -0
  156. package/registry/frameworks/nuxt/server/utils/runtime.ts +42 -0
  157. package/registry/frameworks/nuxt/server/utils/serialize.ts +30 -0
  158. package/registry/frameworks/nuxt/server/utils/session.ts +210 -0
  159. package/registry/frameworks/nuxt/server/utils/threads.ts +404 -0
  160. package/registry/frameworks/nuxt/tsconfig.json +18 -0
  161. package/registry/frameworks/nuxt.ts +17 -0
  162. package/registry/frameworks/vite/.env.example +20 -0
  163. package/registry/frameworks/vite/README.md +149 -0
  164. package/registry/frameworks/vite/agent/index.ts +59 -0
  165. package/registry/frameworks/vite/agent/middleware.ts +24 -0
  166. package/registry/frameworks/vite/agent/tools.ts +64 -0
  167. package/registry/frameworks/vite/index.html +23 -0
  168. package/registry/frameworks/vite/langgraph.json +16 -0
  169. package/registry/frameworks/vite/package.json +39 -0
  170. package/registry/frameworks/vite/public/favicon.ico +0 -0
  171. package/registry/frameworks/vite/scripts/vite-langgraph-proxy.ts +34 -0
  172. package/registry/frameworks/vite/src/components/Chat.tsx +124 -0
  173. package/registry/frameworks/vite/src/components/ChatApp.tsx +122 -0
  174. package/registry/frameworks/vite/src/components/Conversation.tsx +91 -0
  175. package/registry/frameworks/vite/src/components/MessageBubbles.tsx +88 -0
  176. package/registry/frameworks/vite/src/components/MessageReasoning.tsx +71 -0
  177. package/registry/frameworks/vite/src/components/MessageThread.tsx +135 -0
  178. package/registry/frameworks/vite/src/components/StreamingIndicator.tsx +36 -0
  179. package/registry/frameworks/vite/src/components/Subagents.tsx +120 -0
  180. package/registry/frameworks/vite/src/components/ThemeIcons.tsx +31 -0
  181. package/registry/frameworks/vite/src/components/ThreadHistory.tsx +73 -0
  182. package/registry/frameworks/vite/src/components/ToolCall.tsx +89 -0
  183. package/registry/frameworks/vite/src/lib/agent-type.ts +4 -0
  184. package/registry/frameworks/vite/src/lib/chat/threads-client.ts +114 -0
  185. package/registry/frameworks/vite/src/main.tsx +11 -0
  186. package/registry/frameworks/vite/src/styles/globals.css +714 -0
  187. package/registry/frameworks/vite/src/vite-env.d.ts +11 -0
  188. package/registry/frameworks/vite/tsconfig.app.json +24 -0
  189. package/registry/frameworks/vite/tsconfig.json +7 -0
  190. package/registry/frameworks/vite/tsconfig.node.json +21 -0
  191. package/registry/frameworks/vite/vercel.json +3 -0
  192. package/registry/frameworks/vite/vite.config.ts +24 -0
  193. 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,4 @@
1
+ {
2
+ "extends": "../tsconfig.worker.json",
3
+ "include": ["."]
4
+ }
@@ -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,6 @@
1
+ # Required: OpenAI API key used by the agent and its subagents.
2
+ OPENAI_API_KEY=sk-...
3
+
4
+ # Optional: enable LangSmith tracing.
5
+ # LANGSMITH_TRACING=true
6
+ # LANGSMITH_API_KEY=lsv2-...
@@ -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
+ [![Deploy with Vercel](https://vercel.com/button)](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
+ }