@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,22 @@
1
+ /**
2
+ * `GET /api/threads/:threadId/state`.
3
+ *
4
+ * Reads checkpointed thread state. Returns 404 when the thread has no
5
+ * checkpoint yet so the LangGraph SDK can bootstrap it before the first run.
6
+ */
7
+
8
+ import { getAgent } from "../../../utils/runtime";
9
+ import { ThreadNotFoundError, getThreadState } from "../../../utils/threads";
10
+
11
+ export default defineEventHandler(async (event) => {
12
+ const threadId = getRouterParam(event, "threadId") ?? "local";
13
+ try {
14
+ return await getThreadState(getAgent().graph, threadId);
15
+ } catch (error) {
16
+ if (error instanceof ThreadNotFoundError) {
17
+ setResponseStatus(event, 404);
18
+ return { error: "not_found", message: error.message };
19
+ }
20
+ throw error;
21
+ }
22
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `POST /api/threads/:threadId/state`.
3
+ *
4
+ * Creates or updates checkpointed thread state. Used by the browser bootstrap
5
+ * and by the SDK when hydrating or editing conversation history.
6
+ */
7
+
8
+ import { getAgent } from "../../../utils/runtime";
9
+ import { updateThreadState } from "../../../utils/threads";
10
+
11
+ type StateUpdateBody = {
12
+ values?: Record<string, unknown> | null;
13
+ checkpoint?: Record<string, unknown> | null;
14
+ as_node?: string;
15
+ };
16
+
17
+ export default defineEventHandler(async (event) => {
18
+ const threadId = getRouterParam(event, "threadId") ?? "local";
19
+ const body = await readBody<StateUpdateBody>(event).catch(
20
+ () => ({}) as StateUpdateBody,
21
+ );
22
+ try {
23
+ return await updateThreadState(getAgent().graph, threadId, {
24
+ values: body.values ?? null,
25
+ checkpoint: body.checkpoint ?? null,
26
+ asNode: body.as_node,
27
+ });
28
+ } catch (error) {
29
+ setResponseStatus(event, 422);
30
+ return { error: "invalid_state_update", message: String(error) };
31
+ }
32
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `POST /api/threads/:threadId/stream`.
3
+ *
4
+ * The request body is a connection-scoped `SubscribeParams` filter. The
5
+ * response is an SSE stream that first replays matching buffered events and
6
+ * then stays attached for live events from the same thread.
7
+ */
8
+
9
+ import type { SubscribeParams } from "@langchain/protocol";
10
+
11
+ import { getSession } from "../../../utils/runtime";
12
+
13
+ export default defineEventHandler(async (event) => {
14
+ const threadId = getRouterParam(event, "threadId") ?? "local";
15
+ const params = await readBody<SubscribeParams>(event);
16
+
17
+ setResponseHeader(event, "content-type", "text/event-stream");
18
+ setResponseHeader(event, "cache-control", "no-cache");
19
+ setResponseHeader(event, "connection", "keep-alive");
20
+ // Disable proxy buffering so SSE frames flush immediately.
21
+ setResponseHeader(event, "x-accel-buffering", "no");
22
+
23
+ return getSession(threadId).stream(params);
24
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `GET /api/threads` — list every thread known to the checkpointer.
3
+ *
4
+ * The agent's in-memory `MemorySaver` is the single source of truth, so the
5
+ * sidebar is always derived from it (no client-side cache).
6
+ */
7
+
8
+ import { getAgent, getCheckpointer } from "../../utils/runtime";
9
+ import { listThreads } from "../../utils/threads";
10
+
11
+ export default defineEventHandler(async () => {
12
+ return listThreads(getAgent().graph, getCheckpointer());
13
+ });
@@ -0,0 +1,42 @@
1
+ import { agent, checkpointer } from "../agent";
2
+ import { LocalThreadSession } from "./session";
3
+
4
+ /**
5
+ * Process-local registry for the agent and its per-thread sessions.
6
+ *
7
+ * Under Nuxt the Agent Streaming Protocol is served by Nitro route handlers, so
8
+ * the shared agent + session registry is a module singleton instead of a
9
+ * standalone Hono server.
10
+ *
11
+ * NOTE: This is in-memory and process-local. A serverless/multi-instance
12
+ * deployment needs a durable checkpointer (Postgres, SQLite, …) and a shared
13
+ * session/replay store. The wiring here stays the same; only the checkpointer
14
+ * in `server/agent/index.ts` and this store change.
15
+ */
16
+ const sessions = new Map<string, LocalThreadSession>();
17
+
18
+ /** The shared, compiled agent (and its checkpointer). */
19
+ export function getAgent() {
20
+ return agent;
21
+ }
22
+
23
+ /** The shared checkpointer — the single source of truth for threads. */
24
+ export function getCheckpointer() {
25
+ return checkpointer;
26
+ }
27
+
28
+ /** Get or create the process-local session for a thread. */
29
+ export function getSession(threadId: string): LocalThreadSession {
30
+ let session = sessions.get(threadId);
31
+ if (session == null) {
32
+ session = new LocalThreadSession(agent, threadId);
33
+ sessions.set(threadId, session);
34
+ }
35
+ return session;
36
+ }
37
+
38
+ /** Delete a thread: remove its session and its checkpointed state. */
39
+ export async function deleteThread(threadId: string): Promise<void> {
40
+ sessions.delete(threadId);
41
+ await checkpointer.deleteThread(threadId);
42
+ }
@@ -0,0 +1,30 @@
1
+ import { BaseMessage } from "@langchain/core/messages";
2
+
3
+ export function isRecord(value: unknown): value is Record<string, unknown> {
4
+ return typeof value === "object" && value !== null;
5
+ }
6
+
7
+ /**
8
+ * Recursively replace LangChain message instances with plain protocol dicts.
9
+ *
10
+ * Uses {@link BaseMessage.isInstance} and {@link BaseMessage.toDict} from
11
+ * `@langchain/core/messages` — the canonical LangChain serialization primitive.
12
+ * Message instances surface in `values`/`updates` stream data and in the
13
+ * checkpointer state snapshot returned by the thread-state routes, often nested
14
+ * under `messages`.
15
+ */
16
+ export function sanitizeForJson(value: unknown): unknown {
17
+ if (BaseMessage.isInstance(value)) {
18
+ const { type, data } = value.toDict();
19
+ return sanitizeForJson({ ...data, type });
20
+ }
21
+ if (Array.isArray(value)) return value.map(sanitizeForJson);
22
+ if (isRecord(value)) {
23
+ const result: Record<string, unknown> = {};
24
+ for (const [key, item] of Object.entries(value)) {
25
+ result[key] = sanitizeForJson(item);
26
+ }
27
+ return result;
28
+ }
29
+ return value;
30
+ }
@@ -0,0 +1,210 @@
1
+ import type { ReactAgent } from "langchain";
2
+ // `StreamChannel` buffers events; `matchesSubscription` is the shared protocol
3
+ // predicate from `@langchain/langgraph/stream` — the same one langgraph-api
4
+ // uses, so this custom transport stays aligned with the production server.
5
+ import {
6
+ StreamChannel,
7
+ matchesSubscription,
8
+ type ProtocolEvent,
9
+ } from "@langchain/langgraph/stream";
10
+ import type {
11
+ Command,
12
+ CommandResponse,
13
+ ErrorResponse,
14
+ SubscribeParams,
15
+ } from "@langchain/protocol";
16
+
17
+ import { isRecord, sanitizeForJson } from "./serialize";
18
+
19
+ // `ReactAgent<any>` accepts both `createAgent` results and `DeepAgent`
20
+ // instances (which carry a specific, non-default type config).
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ type AnyReactAgent = ReactAgent<any>;
23
+
24
+ type AgentRunInput = Parameters<AnyReactAgent["streamEvents"]>[0];
25
+
26
+ /**
27
+ * Make an event safe to `JSON.stringify` onto the SSE wire.
28
+ *
29
+ * Only the protocol payload (`params.data`) and any `params.interrupts` can
30
+ * carry LangChain message instances, so those are the fields we sanitize into
31
+ * the plain, role-keyed protocol message shape the SDK expects.
32
+ */
33
+ function sanitizeEvent(event: ProtocolEvent): ProtocolEvent {
34
+ const params = event.params as Record<string, unknown>;
35
+ const sanitizedParams: Record<string, unknown> = {
36
+ ...params,
37
+ data: sanitizeForJson(params.data),
38
+ };
39
+ if ("interrupts" in params) {
40
+ sanitizedParams.interrupts = sanitizeForJson(params.interrupts);
41
+ }
42
+ return { ...event, params: sanitizedParams } as ProtocolEvent;
43
+ }
44
+
45
+ /**
46
+ * Encode an Agent Protocol event as a Server-Sent Event frame.
47
+ *
48
+ * When available, `event_id` is mirrored into the SSE `id:` field for
49
+ * transport-level reconnection. The SDK primarily deduplicates by `event_id`
50
+ * and replays by `seq`; if an event has no `event_id`, this example falls back
51
+ * to `seq` as a stable frame id.
52
+ */
53
+ function encodeSse(event: ProtocolEvent) {
54
+ const eventId = (event as { event_id?: string }).event_id;
55
+ const id = eventId ?? (typeof event.seq === "number" ? `${event.seq}` : "");
56
+ const idLine = id ? `id: ${id}\n` : "";
57
+ return new TextEncoder().encode(
58
+ `${idLine}event: message\ndata: ${JSON.stringify(event)}\n\n`,
59
+ );
60
+ }
61
+
62
+ /**
63
+ * Minimal in-memory Agent Streaming Protocol session for the example.
64
+ *
65
+ * This class is the server-side counterpart to `HttpAgentServerAdapter`:
66
+ *
67
+ * - `POST /threads/:thread_id/commands` sends a JSON `Command` and receives a
68
+ * `CommandResponse` or `ErrorResponse`.
69
+ * - `POST /threads/:thread_id/stream` opens a connection-scoped SSE
70
+ * subscription described by `SubscribeParams`.
71
+ * - Events are buffered by `seq` and replayed to later subscriptions, enabling
72
+ * the SDK to rotate streams as subscriptions widen or narrow.
73
+ *
74
+ * The implementation is intentionally small and process-local. It is suitable
75
+ * for this example and for understanding the protocol shape, but production
76
+ * servers should persist threads, enforce concurrency policies, and coordinate
77
+ * replay buffers across workers.
78
+ */
79
+ export class LocalThreadSession {
80
+ readonly #agent: AnyReactAgent;
81
+ readonly #threadId: string;
82
+
83
+ /**
84
+ * Per-thread protocol event log.
85
+ *
86
+ * A {@link StreamChannel} is LangGraph's buffered, append-only stream with
87
+ * independent per-consumer cursors. Every event ever published stays
88
+ * buffered, and each SSE subscription gets its own cursor via
89
+ * {@link StreamChannel.iterate}, so buffered replay and live delivery are the
90
+ * same iteration.
91
+ */
92
+ readonly #log = StreamChannel.local<ProtocolEvent>();
93
+
94
+ /** Monotonic seq across all runs on this thread (graph runs reset at 0). */
95
+ #nextSeq = 0;
96
+
97
+ #activeRun:
98
+ | {
99
+ abort(reason?: unknown): void;
100
+ }
101
+ | undefined;
102
+
103
+ constructor(agent: AnyReactAgent, threadId: string) {
104
+ this.#agent = agent;
105
+ this.#threadId = threadId;
106
+ }
107
+
108
+ /**
109
+ * Handle a thread command sent to the Agent Protocol `/commands` endpoint.
110
+ *
111
+ * The SDK sends `run.start` to start or resume a graph run on the current
112
+ * thread. This starts the in-process v3 stream and immediately returns a
113
+ * success response containing a generated `run_id`, while streamed events
114
+ * flow asynchronously through active `/stream` subscriptions.
115
+ */
116
+ async handleCommand(
117
+ command: Command,
118
+ ): Promise<CommandResponse | ErrorResponse> {
119
+ if (command.method !== "run.start") {
120
+ return {
121
+ type: "error",
122
+ id: command.id,
123
+ error: "unknown_command",
124
+ message: `Unsupported command: ${command.method}`,
125
+ } as ErrorResponse;
126
+ }
127
+
128
+ const params = isRecord(command.params)
129
+ ? (command.params as { input?: unknown })
130
+ : {};
131
+ const runId = crypto.randomUUID();
132
+ void this.#startRun(params.input as AgentRunInput, runId);
133
+
134
+ return {
135
+ type: "success",
136
+ id: command.id,
137
+ result: { run_id: runId },
138
+ } as CommandResponse;
139
+ }
140
+
141
+ /**
142
+ * Open a connection-scoped SSE subscription for this thread.
143
+ *
144
+ * The returned `ReadableStream` first replays buffered events matching the
145
+ * requested `channels`, `namespaces`, `depth`, and optional `since` cursor,
146
+ * then stays attached for live events. Closing the HTTP connection releases
147
+ * this subscription's event-log cursor.
148
+ */
149
+ stream(params: SubscribeParams) {
150
+ const cursor = this.#log.iterate();
151
+
152
+ return new ReadableStream<Uint8Array>({
153
+ pull: async (controller) => {
154
+ // Scan forward until we find an event matching this subscription's
155
+ // filter, enqueue exactly one frame, and return so the channel honors
156
+ // the consumer's backpressure. `cursor.next()` resolves immediately for
157
+ // buffered events and suspends once the live edge is reached.
158
+ for (;;) {
159
+ const { value: event, done } = await cursor.next();
160
+ if (done) {
161
+ controller.close();
162
+ return;
163
+ }
164
+ if (matchesSubscription(event, params)) {
165
+ controller.enqueue(encodeSse(event));
166
+ return;
167
+ }
168
+ }
169
+ },
170
+ cancel: () => {
171
+ void cursor.return?.(undefined);
172
+ },
173
+ });
174
+ }
175
+
176
+ #publish(rawEvent: ProtocolEvent) {
177
+ const seq = this.#nextSeq;
178
+ this.#nextSeq += 1;
179
+ const event = sanitizeEvent({
180
+ ...rawEvent,
181
+ type: "event",
182
+ seq,
183
+ } as ProtocolEvent);
184
+ this.#log.push(event);
185
+ }
186
+
187
+ async #startRun(input: AgentRunInput, runId: string) {
188
+ this.#activeRun?.abort("Starting a new run.");
189
+ // Thread the `thread_id` / `run_id` into the run config so the checkpointer
190
+ // persists conversation state per thread and downstream events carry the
191
+ // run identity.
192
+ const run = await this.#agent.streamEvents(input, {
193
+ version: "v3",
194
+ configurable: { thread_id: this.#threadId, run_id: runId },
195
+ });
196
+ this.#activeRun = run;
197
+
198
+ try {
199
+ for await (const rawEvent of run) {
200
+ this.#publish(rawEvent as ProtocolEvent);
201
+ }
202
+ } catch (error) {
203
+ console.error(error);
204
+ } finally {
205
+ if (this.#activeRun === run) {
206
+ this.#activeRun = undefined;
207
+ }
208
+ }
209
+ }
210
+ }