@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.ts";
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,17 @@
1
+ import { createFramework } from "../../src/registry/framework.js";
2
+
3
+ export const deno = createFramework({
4
+ id: "deno",
5
+ title: "Deno",
6
+ defaultProjectName: "deno-deepagents",
7
+ address: {
8
+ scheme: "github",
9
+ owner: "aolsenjazz",
10
+ repo: "deployment-cookbook",
11
+ subPath: "js-deno",
12
+ },
13
+ envFilePath: ".env",
14
+ packageJsonPath: "package.json",
15
+ agentPath: "server/agent",
16
+ files: [],
17
+ });
@@ -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,186 @@
1
+ # Deploying a LangChain Agent with Cloudflare Workers
2
+
3
+ An example app that deploys a LangChain **deep agent** on
4
+ [Cloudflare Workers](https://developers.cloudflare.com/workers/) — streaming chat
5
+ UI, subagents, and thread history, all backed by the
6
+ [Agent Streaming Protocol](https://github.com/langchain-ai/agent-protocol/tree/main/streaming) implemented as Worker
7
+ routes (HTTP + SSE). The React SPA is served from the same Worker via
8
+ [Workers Assets](https://developers.cloudflare.com/workers/static-assets/). No
9
+ separate backend process — one Worker serves the SPA and the protocol API.
10
+
11
+ ## Deploy to Cloudflare
12
+
13
+ 1. Install dependencies and build:
14
+
15
+ ```bash
16
+ cd js-cloudflare
17
+ cp .env.example .dev.vars # set OPENAI_API_KEY for local dev
18
+ pnpm install
19
+ pnpm build
20
+ ```
21
+
22
+ 2. Log in and set your secret:
23
+
24
+ ```bash
25
+ npx wrangler login
26
+ npx wrangler secret put OPENAI_API_KEY
27
+ ```
28
+
29
+ 3. Deploy:
30
+
31
+ ```bash
32
+ pnpm run deploy
33
+ ```
34
+
35
+ Wrangler uploads the Vite build (SPA) and the Worker script in one deploy.
36
+ `nodejs_compat` and `nodejs_compat_populate_process_env` are enabled so LangChain
37
+ can read `OPENAI_API_KEY` from the environment.
38
+
39
+ `wrangler.jsonc` registers the `ThreadSession` Durable Object with
40
+ `new_sqlite_classes`, which is required on the Workers **Free** plan (error
41
+ `10097` if you use the legacy `new_classes` migration instead).
42
+
43
+ Optionally enable LangSmith tracing by adding the variables from
44
+ [`.env.example`](./.env.example) as Worker secrets or vars.
45
+
46
+ ## Required API endpoints
47
+
48
+ The app exposes the Agent Streaming Protocol under `/api/threads/...`. Routes
49
+ are implemented in `worker/index.ts` with [Hono](https://hono.dev).
50
+
51
+ ### Minimum (streaming chat)
52
+
53
+ | Method | Path | Purpose |
54
+ | -------------- | --------------------------------- | -------------------------------------------------------------- |
55
+ | `POST` | `/api/threads/:threadId/commands` | Accept protocol commands (`run.start`, …) and start agent runs |
56
+ | `POST` | `/api/threads/:threadId/stream` | SSE stream of protocol events for a run |
57
+ | `GET` / `POST` | `/api/threads/:threadId/state` | Read and bootstrap checkpointed thread state |
58
+
59
+ ### Optional (sidebar)
60
+
61
+ | Method | Path | Purpose |
62
+ | -------- | -------------------------------- | ----------------------------------------- |
63
+ | `GET` | `/api/threads` | List threads known to the checkpointer |
64
+ | `DELETE` | `/api/threads/:threadId` | Delete a thread's session and checkpoints |
65
+ | `POST` | `/api/threads/:threadId/history` | Paginated checkpoint history |
66
+
67
+ ### Request flow
68
+
69
+ ```mermaid
70
+ flowchart TB
71
+ subgraph browser["Browser (Vite + React)"]
72
+ SP["StreamProvider"]
73
+ HAA["HttpAgentServerAdapter"]
74
+ SP --- HAA
75
+ end
76
+
77
+ subgraph worker["Cloudflare Worker (Hono)"]
78
+ CMD["POST /api/threads/:id/commands"]
79
+ STR["POST /api/threads/:id/stream"]
80
+ STA["GET|POST /api/threads/:id/state"]
81
+ RUN["startAgentRun"]
82
+ end
83
+
84
+ subgraph do["Durable Object (per thread)"]
85
+ LOG["StreamChannel event log"]
86
+ SSE["SSE subscriptions"]
87
+ end
88
+
89
+ subgraph agent["worker/agent"]
90
+ AGT["createDeepAgent + MemorySaver"]
91
+ end
92
+
93
+ HAA -->|POST| CMD
94
+ HAA -->|POST| STR
95
+ HAA -->|GET / POST| STA
96
+ CMD --> RUN
97
+ RUN --> AGT
98
+ RUN -->|publish events| LOG
99
+ STR --> SSE
100
+ LOG --> SSE
101
+ STA --> AGT
102
+ ```
103
+
104
+ 1. Bootstrap thread state (`GET`/`POST /state`).
105
+ 2. On submit, the SDK sends `run.start` to `/commands` and receives a `run_id`.
106
+ 3. The Worker starts the graph run and fans each protocol event into the
107
+ thread's **Durable Object**.
108
+ 4. The SDK subscribes to `/stream` (SSE). The DO replays buffered events and
109
+ stays attached for live frames — even across Worker isolate restarts.
110
+ 5. Subagent (`task`) runs emit namespaced events surfaced as `stream.subagents`.
111
+
112
+ ## Cloudflare backend design
113
+
114
+ | Concern | Implementation |
115
+ | ------------- | ------------------------------------------------------- |
116
+ | Frontend | Vite + React SPA (`src/`) |
117
+ | API layer | Hono routes in `worker/index.ts` |
118
+ | Runtime | Workers V8 + `nodejs_compat` |
119
+ | SSE replay | Per-thread **Durable Object** (`ThreadSession`) |
120
+ | Agent runs | Worker isolate; protocol events POSTed to the DO |
121
+ | Static assets | Workers Assets (`wrangler.jsonc` → `assets`) |
122
+ | Secrets | `wrangler secret` / `.dev.vars` |
123
+ | Local dev | `vite` (Cloudflare Vite plugin runs the Worker runtime) |
124
+
125
+ The split between **Worker** (agent + checkpointer) and **Durable Object** (SSE
126
+ event log) is the main design choice on Cloudflare. Worker isolates are
127
+ ephemeral, so replay buffers live in Durable Objects rather than process memory.
128
+
129
+ ## Production persistence
130
+
131
+ Out of the box, the agent uses an in-memory `MemorySaver` checkpointer
132
+ (`worker/agent/index.ts`). That works for local dev and demos, but on Cloudflare
133
+ (multiple isolates, cold starts) conversation state is **not durable** across
134
+ deploys or isolates.
135
+
136
+ For production:
137
+
138
+ 1. Swap in a [durable checkpointer](https://docs.langchain.com/oss/javascript/langgraph/checkpointers#checkpointer-libraries)
139
+ (for example Postgres via Hyperdrive, or a custom DO-backed store).
140
+ 2. Keep per-thread Durable Objects for SSE replay (or persist the event log to
141
+ DO storage / KV for long-lived reconnects).
142
+
143
+ See also: [checkpointer libraries](https://docs.langchain.com/oss/javascript/langgraph/checkpointers#checkpointer-libraries),
144
+ [add memory / persistence](https://docs.langchain.com/oss/javascript/langgraph/add-memory).
145
+
146
+ ## Local development
147
+
148
+ ```bash
149
+ cp .env.example .dev.vars # set OPENAI_API_KEY
150
+ pnpm install
151
+ pnpm dev
152
+ ```
153
+
154
+ Open [http://localhost:5173](http://localhost:5173). The Cloudflare Vite plugin
155
+ runs your Worker in the Workers runtime during dev, so `/api/*` routes behave
156
+ like production.
157
+
158
+ ```bash
159
+ pnpm build # production build (client + worker)
160
+ pnpm preview # preview the production build locally
161
+ pnpm typecheck
162
+ ```
163
+
164
+ ## Project layout
165
+
166
+ - `src/components/` — chat UI (`ChatApp`, `Chat`, `MessageThread`, `Subagents`,
167
+ `ThreadHistory`, …).
168
+ - `src/lib/chat/threads-client.ts` — browser thread bootstrap and sidebar helpers.
169
+ - `worker/agent/` — deep agent (`createDeepAgent`) with `researcher` and
170
+ `math-whiz` subagents and mock tools.
171
+ - `worker/server/` — protocol helpers: `runs.ts` (start runs on the Worker),
172
+ `threads.ts` (checkpointer-backed state), `serialize.ts`, `registry.ts`.
173
+ - `worker/durable-objects/thread-session.ts` — per-thread SSE event log
174
+ (`StreamChannel` + `matchesSubscription`).
175
+ - `worker/index.ts` — Hono app: protocol routes + Worker export.
176
+ - `wrangler.jsonc` — Worker config: `nodejs_compat`, Durable Object bindings,
177
+ SPA asset routing (`run_worker_first: ["/api/*"]`).
178
+
179
+ ## References
180
+
181
+ - [Agent Streaming Protocol](https://github.com/langchain-ai/agent-protocol/tree/main/streaming) — protocol spec consumed by `@langchain/react`'s `HttpAgentServerAdapter`
182
+ - [`react-custom-backend`](https://github.com/langchain-ai/streaming-cookbook) — original Vite + Hono reference for a custom protocol server
183
+ - [Cloudflare Workers](https://developers.cloudflare.com/workers/) — edge runtime and deployment
184
+ - [Durable Objects](https://developers.cloudflare.com/durable-objects/) — per-thread SSE replay storage
185
+ - [Workers Assets](https://developers.cloudflare.com/workers/static-assets/) — SPA hosting from the same Worker
186
+ - [`deepagents`](https://www.npmjs.com/package/deepagents) — coordinator + subagent orchestration used by this demo
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html lang="en" class="h-full antialiased">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>LangChain Deep Agent · Cloudflare Workers</title>
7
+ <meta
8
+ name="description"
9
+ content="Deploying a LangChain deep agent with Cloudflare Workers: streaming chat, subagents, and per-thread history."
10
+ />
11
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
13
+ <link
14
+ href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500&family=Geist:wght@400;500;600&display=swap"
15
+ rel="stylesheet"
16
+ />
17
+ </head>
18
+ <body class="min-h-full">
19
+ <div id="root" class="h-full"></div>
20
+ <script type="module" src="/src/main.tsx"></script>
21
+ </body>
22
+ </html>
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "hono-deepagents",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "deploy": "pnpm build && wrangler deploy",
11
+ "typecheck": "tsc -b --noEmit",
12
+ "lint": "eslint ."
13
+ },
14
+ "dependencies": {
15
+ "@langchain/core": "^1.2.1",
16
+ "@langchain/langgraph": "^1.3.7",
17
+ "@langchain/langgraph-sdk": "^1.9.20",
18
+ "@langchain/openai": "^1.4.7",
19
+ "@langchain/protocol": "^0.0.16",
20
+ "@langchain/react": "^1.0.20",
21
+ "deepagents": "^1.10.2",
22
+ "hono": "^4.11.4",
23
+ "langchain": "^1.4.4",
24
+ "react": "19.2.4",
25
+ "react-dom": "19.2.4",
26
+ "zod": "^4.4.3"
27
+ },
28
+ "devDependencies": {
29
+ "@cloudflare/vite-plugin": "^1.7.0",
30
+ "@cloudflare/workers-types": "^4.20250613.0",
31
+ "@tailwindcss/vite": "^4.1.11",
32
+ "@types/react": "^19",
33
+ "@types/react-dom": "^19",
34
+ "@vitejs/plugin-react": "^5.1.0",
35
+ "eslint": "^9",
36
+ "tailwindcss": "^4",
37
+ "typescript": "^5",
38
+ "vite": "^7.1.12",
39
+ "wrangler": "^4.20.0"
40
+ },
41
+ "packageManager": "pnpm@10.29.2"
42
+ }