@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,212 @@
1
+ import "server-only";
2
+
3
+ import type { ReactAgent } from "langchain";
4
+ // `StreamChannel` buffers events; `matchesSubscription` is the shared protocol
5
+ // predicate from `@langchain/langgraph/stream` — the same one langgraph-api
6
+ // uses, so this custom transport stays aligned with the production server.
7
+ import {
8
+ StreamChannel,
9
+ matchesSubscription,
10
+ type ProtocolEvent,
11
+ } from "@langchain/langgraph/stream";
12
+ import type {
13
+ Command,
14
+ CommandResponse,
15
+ ErrorResponse,
16
+ SubscribeParams,
17
+ } from "@langchain/protocol";
18
+
19
+ import { isRecord, sanitizeForJson } from "./serialize";
20
+
21
+ // `ReactAgent<any>` accepts both `createAgent` results and `DeepAgent`
22
+ // instances (which carry a specific, non-default type config).
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ type AnyReactAgent = ReactAgent<any>;
25
+
26
+ type AgentRunInput = Parameters<AnyReactAgent["streamEvents"]>[0];
27
+
28
+ /**
29
+ * Make an event safe to `JSON.stringify` onto the SSE wire.
30
+ *
31
+ * Only the protocol payload (`params.data`) and any `params.interrupts` can
32
+ * carry LangChain message instances, so those are the fields we sanitize into
33
+ * the plain, role-keyed protocol message shape the SDK expects.
34
+ */
35
+ function sanitizeEvent(event: ProtocolEvent): ProtocolEvent {
36
+ const params = event.params as Record<string, unknown>;
37
+ const sanitizedParams: Record<string, unknown> = {
38
+ ...params,
39
+ data: sanitizeForJson(params.data),
40
+ };
41
+ if ("interrupts" in params) {
42
+ sanitizedParams.interrupts = sanitizeForJson(params.interrupts);
43
+ }
44
+ return { ...event, params: sanitizedParams } as ProtocolEvent;
45
+ }
46
+
47
+ /**
48
+ * Encode an Agent Protocol event as a Server-Sent Event frame.
49
+ *
50
+ * When available, `event_id` is mirrored into the SSE `id:` field for
51
+ * transport-level reconnection. The SDK primarily deduplicates by `event_id`
52
+ * and replays by `seq`; if an event has no `event_id`, this example falls back
53
+ * to `seq` as a stable frame id.
54
+ */
55
+ function encodeSse(event: ProtocolEvent) {
56
+ const eventId = (event as { event_id?: string }).event_id;
57
+ const id = eventId ?? (typeof event.seq === "number" ? `${event.seq}` : "");
58
+ const idLine = id ? `id: ${id}\n` : "";
59
+ return new TextEncoder().encode(
60
+ `${idLine}event: message\ndata: ${JSON.stringify(event)}\n\n`,
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Minimal in-memory Agent Streaming Protocol session for the example.
66
+ *
67
+ * This class is the server-side counterpart to `HttpAgentServerAdapter`:
68
+ *
69
+ * - `POST /threads/:thread_id/commands` sends a JSON `Command` and receives a
70
+ * `CommandResponse` or `ErrorResponse`.
71
+ * - `POST /threads/:thread_id/stream` opens a connection-scoped SSE
72
+ * subscription described by `SubscribeParams`.
73
+ * - Events are buffered by `seq` and replayed to later subscriptions, enabling
74
+ * the SDK to rotate streams as subscriptions widen or narrow.
75
+ *
76
+ * The implementation is intentionally small and process-local. It is suitable
77
+ * for this example and for understanding the protocol shape, but production
78
+ * servers should persist threads, enforce concurrency policies, and coordinate
79
+ * replay buffers across workers.
80
+ */
81
+ export class LocalThreadSession {
82
+ readonly #agent: AnyReactAgent;
83
+ readonly #threadId: string;
84
+
85
+ /**
86
+ * Per-thread protocol event log.
87
+ *
88
+ * A {@link StreamChannel} is LangGraph's buffered, append-only stream with
89
+ * independent per-consumer cursors. Every event ever published stays
90
+ * buffered, and each SSE subscription gets its own cursor via
91
+ * {@link StreamChannel.iterate}, so buffered replay and live delivery are the
92
+ * same iteration.
93
+ */
94
+ readonly #log = StreamChannel.local<ProtocolEvent>();
95
+
96
+ /** Monotonic seq across all runs on this thread (graph runs reset at 0). */
97
+ #nextSeq = 0;
98
+
99
+ #activeRun:
100
+ | {
101
+ abort(reason?: unknown): void;
102
+ }
103
+ | undefined;
104
+
105
+ constructor(agent: AnyReactAgent, threadId: string) {
106
+ this.#agent = agent;
107
+ this.#threadId = threadId;
108
+ }
109
+
110
+ /**
111
+ * Handle a thread command sent to the Agent Protocol `/commands` endpoint.
112
+ *
113
+ * The SDK sends `run.start` to start or resume a graph run on the current
114
+ * thread. This starts the in-process v3 stream and immediately returns a
115
+ * success response containing a generated `run_id`, while streamed events
116
+ * flow asynchronously through active `/stream` subscriptions.
117
+ */
118
+ async handleCommand(
119
+ command: Command,
120
+ ): Promise<CommandResponse | ErrorResponse> {
121
+ if (command.method !== "run.start") {
122
+ return {
123
+ type: "error",
124
+ id: command.id,
125
+ error: "unknown_command",
126
+ message: `Unsupported command: ${command.method}`,
127
+ } as ErrorResponse;
128
+ }
129
+
130
+ const params = isRecord(command.params)
131
+ ? (command.params as { input?: unknown })
132
+ : {};
133
+ const runId = crypto.randomUUID();
134
+ void this.#startRun(params.input as AgentRunInput, runId);
135
+
136
+ return {
137
+ type: "success",
138
+ id: command.id,
139
+ result: { run_id: runId },
140
+ } as CommandResponse;
141
+ }
142
+
143
+ /**
144
+ * Open a connection-scoped SSE subscription for this thread.
145
+ *
146
+ * The returned `ReadableStream` first replays buffered events matching the
147
+ * requested `channels`, `namespaces`, `depth`, and optional `since` cursor,
148
+ * then stays attached for live events. Closing the HTTP connection releases
149
+ * this subscription's event-log cursor.
150
+ */
151
+ stream(params: SubscribeParams) {
152
+ const cursor = this.#log.iterate();
153
+
154
+ return new ReadableStream<Uint8Array>({
155
+ pull: async (controller) => {
156
+ // Scan forward until we find an event matching this subscription's
157
+ // filter, enqueue exactly one frame, and return so the channel honors
158
+ // the consumer's backpressure. `cursor.next()` resolves immediately for
159
+ // buffered events and suspends once the live edge is reached.
160
+ for (;;) {
161
+ const { value: event, done } = await cursor.next();
162
+ if (done) {
163
+ controller.close();
164
+ return;
165
+ }
166
+ if (matchesSubscription(event, params)) {
167
+ controller.enqueue(encodeSse(event));
168
+ return;
169
+ }
170
+ }
171
+ },
172
+ cancel: () => {
173
+ void cursor.return?.(undefined);
174
+ },
175
+ });
176
+ }
177
+
178
+ #publish(rawEvent: ProtocolEvent) {
179
+ const seq = this.#nextSeq;
180
+ this.#nextSeq += 1;
181
+ const event = sanitizeEvent({
182
+ ...rawEvent,
183
+ type: "event",
184
+ seq,
185
+ } as ProtocolEvent);
186
+ this.#log.push(event);
187
+ }
188
+
189
+ async #startRun(input: AgentRunInput, runId: string) {
190
+ this.#activeRun?.abort("Starting a new run.");
191
+ // Thread the `thread_id` / `run_id` into the run config so the checkpointer
192
+ // persists conversation state per thread and downstream events carry the
193
+ // run identity.
194
+ const run = await this.#agent.streamEvents(input, {
195
+ version: "v3",
196
+ configurable: { thread_id: this.#threadId, run_id: runId },
197
+ });
198
+ this.#activeRun = run;
199
+
200
+ try {
201
+ for await (const rawEvent of run) {
202
+ this.#publish(rawEvent as ProtocolEvent);
203
+ }
204
+ } catch (error) {
205
+ console.error(error);
206
+ } finally {
207
+ if (this.#activeRun === run) {
208
+ this.#activeRun = undefined;
209
+ }
210
+ }
211
+ }
212
+ }
@@ -0,0 +1,406 @@
1
+ import "server-only";
2
+
3
+ /**
4
+ * Thread state helpers backed by the graph checkpointer.
5
+ *
6
+ * Implements the LangGraph SDK thread state wire-shape consumed by
7
+ * `client.threads.getState` / `updateState` (`GET|POST /threads/:id/state`) and
8
+ * `getHistory` (`POST /threads/:id/history`), aligned with the Agent Protocol
9
+ * thread model.
10
+ */
11
+
12
+ import type { MemorySaver } from "@langchain/langgraph";
13
+ import type { CompiledGraphType } from "@langchain/langgraph";
14
+ import type { RunnableConfig } from "@langchain/core/runnables";
15
+
16
+ import { isRecord, sanitizeForJson } from "./serialize";
17
+
18
+ /**
19
+ * Compiled LangGraph instance exposed through the custom protocol server.
20
+ *
21
+ * Thread routes read and write checkpointed state through this graph's
22
+ * checkpointer rather than maintaining a separate thread store.
23
+ */
24
+ export type LocalProtocolGraph = CompiledGraphType;
25
+
26
+ type StateSnapshot = Awaited<ReturnType<LocalProtocolGraph["getState"]>>;
27
+
28
+ /**
29
+ * Raised when a thread has no checkpoint yet.
30
+ *
31
+ * The route handlers map this to HTTP 404 so the LangGraph SDK can bootstrap
32
+ * the thread via `POST /threads/:id/state` before the first run.
33
+ */
34
+ export class ThreadNotFoundError extends Error {
35
+ readonly threadId: string;
36
+
37
+ constructor(threadId: string) {
38
+ super(`Thread ${threadId} not found`);
39
+ this.name = "ThreadNotFoundError";
40
+ this.threadId = threadId;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Graph node used when bootstrapping an empty thread.
46
+ *
47
+ * Empty `messages` updates must land on `__start__` so conditional edges are
48
+ * not evaluated before the first human turn exists.
49
+ */
50
+ const INITIAL_UPDATE_NODE = "__start__";
51
+
52
+ /**
53
+ * Default graph node for non-empty state updates on an existing checkpoint.
54
+ *
55
+ * Matches the agent's model node when the client omits `as_node`.
56
+ */
57
+ const DEFAULT_UPDATE_NODE = "model_request";
58
+
59
+ /** Build the {@link RunnableConfig} that scopes graph calls to a thread id. */
60
+ function threadConfig(threadId: string): RunnableConfig {
61
+ return { configurable: { thread_id: threadId } };
62
+ }
63
+
64
+ /**
65
+ * Build the config passed to `getStateHistory` for root or subgraph-scoped
66
+ * history, matching langgraph-api's `{ thread_id, checkpoint_ns: "", ...checkpoint }`.
67
+ */
68
+ function historyConfig(
69
+ threadId: string,
70
+ checkpoint?: Record<string, unknown> | null,
71
+ ): RunnableConfig {
72
+ const configurable: Record<string, unknown> = {
73
+ thread_id: threadId,
74
+ checkpoint_ns: "",
75
+ };
76
+ if (checkpoint && isRecord(checkpoint)) {
77
+ Object.assign(configurable, checkpoint);
78
+ }
79
+ return { configurable };
80
+ }
81
+
82
+ /** Read the `configurable` bag from a LangGraph run config. */
83
+ function configurableOf(config: RunnableConfig): Record<string, unknown> {
84
+ return isRecord(config.configurable) ? config.configurable : {};
85
+ }
86
+
87
+ /**
88
+ * Return whether a {@link StateSnapshot} represents a persisted checkpoint.
89
+ *
90
+ * LangGraph returns an empty configurable bag before the first write; the SDK
91
+ * treats that as "thread not found" rather than an empty thread state.
92
+ */
93
+ function threadHasCheckpoint(snapshot: StateSnapshot): boolean {
94
+ const checkpointId = configurableOf(snapshot.config).checkpoint_id;
95
+ return typeof checkpointId === "string" && checkpointId.length > 0;
96
+ }
97
+
98
+ function isStateSnapshot(state: unknown): state is StateSnapshot {
99
+ return isRecord(state) && "values" in state && "next" in state;
100
+ }
101
+
102
+ function serializeTaskError(error: unknown): string | null {
103
+ if (error == null) return null;
104
+ if (error instanceof Error) return error.message;
105
+ if (typeof error === "string") return error;
106
+ return String(error);
107
+ }
108
+
109
+ /** Map a LangGraph run config to the SDK checkpoint wire shape. */
110
+ function runnableConfigToCheckpoint(
111
+ config: RunnableConfig | null | undefined,
112
+ fallbackThreadId?: string,
113
+ ): Record<string, unknown> | null {
114
+ if (!config || !isRecord(config.configurable)) return null;
115
+ const c = config.configurable;
116
+ const thread_id =
117
+ typeof c.thread_id === "string" ? c.thread_id : fallbackThreadId;
118
+ if (!thread_id || typeof c.checkpoint_id !== "string") return null;
119
+
120
+ return {
121
+ thread_id,
122
+ checkpoint_id: c.checkpoint_id,
123
+ checkpoint_ns: typeof c.checkpoint_ns === "string" ? c.checkpoint_ns : "",
124
+ checkpoint_map: isRecord(c.checkpoint_map) ? c.checkpoint_map : null,
125
+ };
126
+ }
127
+
128
+ function taskCheckpointFromState(
129
+ state: unknown,
130
+ ): Record<string, unknown> | null {
131
+ if (state == null || !isRecord(state) || !isRecord(state.configurable)) {
132
+ return null;
133
+ }
134
+ const c = state.configurable;
135
+ if (typeof c.thread_id !== "string") return null;
136
+ return {
137
+ thread_id: c.thread_id,
138
+ checkpoint_id: typeof c.checkpoint_id === "string" ? c.checkpoint_id : null,
139
+ checkpoint_ns: typeof c.checkpoint_ns === "string" ? c.checkpoint_ns : "",
140
+ checkpoint_map: isRecord(c.checkpoint_map) ? c.checkpoint_map : null,
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Serialize a LangGraph {@link StateSnapshot} to the SDK `ThreadState` shape.
146
+ *
147
+ * Aligned with langgraph-api's `stateSnapshotToThreadState`, plus
148
+ * {@link sanitizeForJson} on `values` and nested task `result` payloads.
149
+ */
150
+ export function serializeThreadState(
151
+ snapshot: StateSnapshot,
152
+ threadId: string,
153
+ ): Record<string, unknown> {
154
+ const configurable = configurableOf(snapshot.config);
155
+ const checkpoint = runnableConfigToCheckpoint(snapshot.config, threadId) ?? {
156
+ thread_id: threadId,
157
+ checkpoint_id:
158
+ typeof configurable.checkpoint_id === "string"
159
+ ? configurable.checkpoint_id
160
+ : null,
161
+ checkpoint_ns:
162
+ typeof configurable.checkpoint_ns === "string"
163
+ ? configurable.checkpoint_ns
164
+ : "",
165
+ checkpoint_map: null,
166
+ };
167
+
168
+ const tasks = (snapshot.tasks ?? []).map((task) => {
169
+ const record = task as {
170
+ id?: unknown;
171
+ name?: unknown;
172
+ error?: unknown;
173
+ interrupts?: unknown;
174
+ path?: unknown;
175
+ result?: unknown;
176
+ state?: unknown;
177
+ };
178
+ return {
179
+ id: record.id,
180
+ name: record.name,
181
+ error: serializeTaskError(record.error),
182
+ interrupts: Array.isArray(record.interrupts) ? record.interrupts : [],
183
+ path: record.path ?? null,
184
+ checkpoint: taskCheckpointFromState(record.state),
185
+ state:
186
+ record.state != null && isStateSnapshot(record.state)
187
+ ? serializeThreadState(record.state, threadId)
188
+ : null,
189
+ result: record.result != null ? sanitizeForJson(record.result) : null,
190
+ };
191
+ });
192
+
193
+ const parentConfig = (snapshot as { parentConfig?: RunnableConfig })
194
+ .parentConfig;
195
+
196
+ return {
197
+ values: sanitizeForJson(snapshot.values ?? {}),
198
+ next: [...(snapshot.next ?? [])],
199
+ tasks,
200
+ checkpoint,
201
+ metadata: { ...snapshot.metadata },
202
+ created_at: snapshot.createdAt ?? null,
203
+ parent_checkpoint: runnableConfigToCheckpoint(parentConfig, threadId),
204
+ };
205
+ }
206
+
207
+ /** Summary of a thread for the history sidebar. */
208
+ export type ThreadSummary = {
209
+ id: string;
210
+ title: string;
211
+ updatedAt: string | null;
212
+ };
213
+
214
+ const UNTITLED = "New conversation";
215
+
216
+ /** Derive a sidebar title from the first human message in a thread. */
217
+ function deriveTitle(values: unknown): string {
218
+ if (!isRecord(values) || !Array.isArray(values.messages)) return UNTITLED;
219
+ for (const message of values.messages) {
220
+ if (!isRecord(message) || message.type !== "human") continue;
221
+ const { content } = message;
222
+ const text =
223
+ typeof content === "string"
224
+ ? content
225
+ : Array.isArray(content)
226
+ ? content
227
+ .map((block) =>
228
+ isRecord(block) && typeof block.text === "string"
229
+ ? block.text
230
+ : "",
231
+ )
232
+ .join("")
233
+ : "";
234
+ const trimmed = text.trim();
235
+ if (trimmed) return trimmed.slice(0, 80);
236
+ }
237
+ return UNTITLED;
238
+ }
239
+
240
+ /**
241
+ * List every thread known to the checkpointer, newest first.
242
+ *
243
+ * The checkpointer is the single source of truth: thread ids are the top-level
244
+ * keys of {@link MemorySaver.storage}, and each thread's title/timestamp is
245
+ * derived from its latest checkpoint. Restarting the server clears all of this.
246
+ */
247
+ export async function listThreads(
248
+ graph: LocalProtocolGraph,
249
+ checkpointer: MemorySaver,
250
+ ): Promise<ThreadSummary[]> {
251
+ const ids = Object.keys(checkpointer.storage);
252
+ const summaries: ThreadSummary[] = [];
253
+ for (const id of ids) {
254
+ try {
255
+ const state = await getThreadState(graph, id);
256
+ summaries.push({
257
+ id,
258
+ title: deriveTitle(state.values),
259
+ updatedAt:
260
+ typeof state.created_at === "string" ? state.created_at : null,
261
+ });
262
+ } catch {
263
+ // Skip threads without a readable checkpoint.
264
+ }
265
+ }
266
+ summaries.sort((a, b) =>
267
+ (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""),
268
+ );
269
+ return summaries;
270
+ }
271
+
272
+ /**
273
+ * Read checkpointed thread state for `GET /threads/:threadId/state`.
274
+ *
275
+ * @throws {@link ThreadNotFoundError} When the thread has no checkpoint yet.
276
+ */
277
+ export async function getThreadState(
278
+ graph: LocalProtocolGraph,
279
+ threadId: string,
280
+ ): Promise<Record<string, unknown>> {
281
+ const snapshot = await graph.getState(threadConfig(threadId));
282
+ if (!threadHasCheckpoint(snapshot)) throw new ThreadNotFoundError(threadId);
283
+ return serializeThreadState(snapshot, threadId);
284
+ }
285
+
286
+ /**
287
+ * Parse the `before` pagination cursor accepted by `POST /threads/:id/history`.
288
+ */
289
+ function parseBeforeCursor(
290
+ threadId: string,
291
+ before: unknown,
292
+ ): RunnableConfig | undefined {
293
+ if (before == null) return undefined;
294
+ if (typeof before === "string") {
295
+ return { configurable: { thread_id: threadId, checkpoint_id: before } };
296
+ }
297
+ if (!isRecord(before)) return undefined;
298
+
299
+ if (isRecord(before.configurable)) {
300
+ return {
301
+ configurable: { thread_id: threadId, ...before.configurable },
302
+ };
303
+ }
304
+
305
+ const checkpointId = before.checkpoint_id;
306
+ if (typeof checkpointId !== "string") return undefined;
307
+
308
+ const cursor: RunnableConfig = {
309
+ configurable: { thread_id: threadId, checkpoint_id: checkpointId },
310
+ };
311
+ if (typeof before.checkpoint_ns === "string") {
312
+ (cursor.configurable as Record<string, unknown>).checkpoint_ns =
313
+ before.checkpoint_ns;
314
+ }
315
+ return cursor;
316
+ }
317
+
318
+ /**
319
+ * List past thread states for `POST /threads/:threadId/history`.
320
+ *
321
+ * @throws {@link ThreadNotFoundError} When the thread has no checkpoint yet.
322
+ */
323
+ export async function getThreadHistory(
324
+ graph: LocalProtocolGraph,
325
+ threadId: string,
326
+ options: {
327
+ limit?: number;
328
+ before?: unknown;
329
+ metadata?: Record<string, unknown>;
330
+ checkpoint?: Record<string, unknown> | null;
331
+ } = {},
332
+ ): Promise<Record<string, unknown>[]> {
333
+ await getThreadState(graph, threadId);
334
+
335
+ const history: Record<string, unknown>[] = [];
336
+ const iterator = graph.getStateHistory(
337
+ historyConfig(threadId, options.checkpoint),
338
+ {
339
+ before: parseBeforeCursor(threadId, options.before),
340
+ limit: options.limit ?? 10,
341
+ ...(options.metadata ? { filter: options.metadata } : {}),
342
+ },
343
+ );
344
+ for await (const snapshot of iterator) {
345
+ history.push(serializeThreadState(snapshot, threadId));
346
+ }
347
+ return history;
348
+ }
349
+
350
+ /** Choose which graph node should receive an `updateState` write. */
351
+ function resolveUpdateNode(options: {
352
+ asNode?: string;
353
+ values: Record<string, unknown> | null;
354
+ hasCheckpoint: boolean;
355
+ }): string {
356
+ if (options.asNode) return options.asNode;
357
+ const messages = options.values?.messages;
358
+ if (!Array.isArray(messages) || messages.length === 0) {
359
+ return INITIAL_UPDATE_NODE;
360
+ }
361
+ if (!options.hasCheckpoint) return INITIAL_UPDATE_NODE;
362
+ return DEFAULT_UPDATE_NODE;
363
+ }
364
+
365
+ /**
366
+ * Create or update thread state for `POST /threads/:threadId/state`.
367
+ *
368
+ * Used by the browser bootstrap and by the SDK when hydrating or editing
369
+ * conversation history. Applies the update at {@link resolveUpdateNode}, then
370
+ * returns the latest serialized snapshot.
371
+ */
372
+ export async function updateThreadState(
373
+ graph: LocalProtocolGraph,
374
+ threadId: string,
375
+ options: {
376
+ values?: Record<string, unknown> | null;
377
+ checkpoint?: Record<string, unknown> | null;
378
+ asNode?: string;
379
+ } = {},
380
+ ): Promise<Record<string, unknown>> {
381
+ let config = threadConfig(threadId);
382
+ const checkpoint = options.checkpoint;
383
+ if (checkpoint && typeof checkpoint.checkpoint_id === "string") {
384
+ config = {
385
+ configurable: {
386
+ ...configurableOf(config),
387
+ checkpoint_id: checkpoint.checkpoint_id,
388
+ ...(typeof checkpoint.checkpoint_ns === "string"
389
+ ? { checkpoint_ns: checkpoint.checkpoint_ns }
390
+ : {}),
391
+ },
392
+ };
393
+ }
394
+
395
+ const snapshot = await graph.getState(config);
396
+ const resolvedValues = options.values ?? { messages: [] };
397
+ const resolvedAsNode = resolveUpdateNode({
398
+ asNode: options.asNode,
399
+ values: resolvedValues,
400
+ hasCheckpoint: threadHasCheckpoint(snapshot),
401
+ });
402
+
403
+ await graph.updateState(config, resolvedValues, resolvedAsNode);
404
+ const updated = await graph.getState(threadConfig(threadId));
405
+ return serializeThreadState(updated, threadId);
406
+ }
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "js-next",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint"
10
+ },
11
+ "dependencies": {
12
+ "@langchain/core": "^1.2.1",
13
+ "@langchain/langgraph": "^1.3.7",
14
+ "@langchain/langgraph-sdk": "^1.9.20",
15
+ "@langchain/openai": "^1.4.7",
16
+ "@langchain/protocol": "^0.0.16",
17
+ "@langchain/react": "^1.0.20",
18
+ "deepagents": "^1.10.2",
19
+ "langchain": "^1.4.4",
20
+ "next": "16.2.9",
21
+ "react": "19.2.4",
22
+ "react-dom": "19.2.4",
23
+ "server-only": "^0.0.1",
24
+ "zod": "^4.4.3"
25
+ },
26
+ "devDependencies": {
27
+ "@tailwindcss/postcss": "^4",
28
+ "@types/node": "^20",
29
+ "@types/react": "^19",
30
+ "@types/react-dom": "^19",
31
+ "eslint": "^9",
32
+ "eslint-config-next": "16.2.9",
33
+ "tailwindcss": "^4",
34
+ "typescript": "^5"
35
+ },
36
+ "packageManager": "pnpm@10.29.2"
37
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>