@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,120 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+
5
+ import type { BaseMessage } from "@langchain/core/messages";
6
+ import type { SubagentDiscoverySnapshot } from "@langchain/langgraph-sdk/stream";
7
+ import { useMessages, useStreamContext } from "@langchain/react";
8
+
9
+ import type { Agent } from "@/lib/agent";
10
+ import { MessageThread } from "./MessageThread";
11
+ import { StreamingIndicator } from "./StreamingIndicator";
12
+
13
+ export type SubagentStatus = SubagentDiscoverySnapshot["status"];
14
+
15
+ /** Lightweight model for a subagent card, derived from a `task` tool call. */
16
+ export type SubagentCard = {
17
+ /** The `task` tool-call id — also the subagent discovery key. */
18
+ id: string;
19
+ name: string;
20
+ task?: string;
21
+ status: SubagentStatus;
22
+ /** Whether a discovery snapshot exists yet (i.e. the card can be opened). */
23
+ openable: boolean;
24
+ };
25
+
26
+ function statusLabel(status: SubagentStatus) {
27
+ if (status === "running") return "Running";
28
+ if (status === "complete") return "Complete";
29
+ return "Error";
30
+ }
31
+
32
+ /** The task prompt is shown separately; skip the matching human message. */
33
+ function omitTaskHumanMessage(
34
+ messages: BaseMessage[],
35
+ taskInput?: string,
36
+ ): BaseMessage[] {
37
+ const task = taskInput?.trim();
38
+ if (!task) return messages;
39
+ return messages.filter(
40
+ (message) => message.type !== "human" || message.text?.trim() !== task,
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Compact, clickable subagent cards showing only the name and task prompt.
46
+ * Rendered inline where the coordinator spawned the subagents. Selecting one
47
+ * drills into its dedicated chat view.
48
+ */
49
+ export function SubagentList({
50
+ cards,
51
+ onOpen,
52
+ }: {
53
+ cards: SubagentCard[];
54
+ onOpen: (id: string) => void;
55
+ }) {
56
+ if (cards.length === 0) return null;
57
+
58
+ return (
59
+ <div aria-label="Subagents" className="subagent-list">
60
+ {cards.map((card) => (
61
+ <button
62
+ className="subagent-chip"
63
+ disabled={!card.openable}
64
+ key={card.id}
65
+ onClick={() => card.openable && onOpen(card.id)}
66
+ type="button"
67
+ >
68
+ <span className="subagent-chip-head">
69
+ <span className="subagent-chip-name">{card.name}</span>
70
+ <span className={`subagent-status status-${card.status}`}>
71
+ {statusLabel(card.status)}
72
+ </span>
73
+ </span>
74
+ {card.task ? (
75
+ <span className="subagent-chip-task">{card.task}</span>
76
+ ) : null}
77
+ </button>
78
+ ))}
79
+ </div>
80
+ );
81
+ }
82
+
83
+ /**
84
+ * The chat interface for a single subagent.
85
+ *
86
+ * `useMessages` is scoped to the subagent's namespace, so its tokens, tool
87
+ * calls, and results stream independently from the root conversation.
88
+ */
89
+ export function SubagentDetail({
90
+ snapshot,
91
+ }: {
92
+ snapshot: SubagentDiscoverySnapshot;
93
+ }) {
94
+ const stream = useStreamContext<Agent>();
95
+ const messages = useMessages(stream, snapshot);
96
+ const visibleMessages = useMemo(
97
+ () => omitTaskHumanMessage(messages, snapshot.taskInput),
98
+ [messages, snapshot.taskInput],
99
+ );
100
+
101
+ return (
102
+ <>
103
+ {snapshot.taskInput ? (
104
+ <div className="subagent-prompt">
105
+ <span>Task</span>
106
+ <p>{snapshot.taskInput}</p>
107
+ </div>
108
+ ) : null}
109
+
110
+ <MessageThread
111
+ isLoading={snapshot.status === "running"}
112
+ messages={visibleMessages}
113
+ />
114
+
115
+ {snapshot.status === "running" && visibleMessages.length === 0 ? (
116
+ <StreamingIndicator />
117
+ ) : null}
118
+ </>
119
+ );
120
+ }
@@ -0,0 +1,31 @@
1
+ export function SunIcon() {
2
+ return (
3
+ <svg
4
+ aria-hidden
5
+ fill="none"
6
+ stroke="currentColor"
7
+ strokeLinecap="round"
8
+ strokeWidth="1.5"
9
+ viewBox="0 0 24 24"
10
+ >
11
+ <circle cx="12" cy="12" r="4" />
12
+ <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
13
+ </svg>
14
+ );
15
+ }
16
+
17
+ export function MoonIcon() {
18
+ return (
19
+ <svg
20
+ aria-hidden
21
+ fill="none"
22
+ stroke="currentColor"
23
+ strokeLinecap="round"
24
+ strokeLinejoin="round"
25
+ strokeWidth="1.5"
26
+ viewBox="0 0 24 24"
27
+ >
28
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
29
+ </svg>
30
+ );
31
+ }
@@ -0,0 +1,73 @@
1
+ "use client";
2
+
3
+ import type { ThreadSummary } from "@/lib/chat/threads-client";
4
+
5
+ function formatTime(updatedAt: string | null) {
6
+ if (!updatedAt) return "";
7
+ const date = new Date(updatedAt);
8
+ if (Number.isNaN(date.getTime())) return "";
9
+ return date.toLocaleString(undefined, {
10
+ month: "short",
11
+ day: "numeric",
12
+ hour: "numeric",
13
+ minute: "2-digit",
14
+ });
15
+ }
16
+
17
+ export function ThreadHistory({
18
+ threads,
19
+ activeThreadId,
20
+ onSelect,
21
+ onCreate,
22
+ onDelete,
23
+ }: {
24
+ threads: ThreadSummary[];
25
+ activeThreadId: string;
26
+ onSelect: (threadId: string) => void;
27
+ onCreate: () => void;
28
+ onDelete: (threadId: string) => void;
29
+ }) {
30
+ return (
31
+ <aside aria-label="Thread history" className="sidebar">
32
+ <div className="sidebar-head">
33
+ <span className="eyebrow">History</span>
34
+ <button className="new-thread" onClick={onCreate} type="button">
35
+ + New
36
+ </button>
37
+ </div>
38
+
39
+ <ul className="thread-list">
40
+ {threads.length === 0 ? (
41
+ <li className="thread-empty">No conversations yet.</li>
42
+ ) : null}
43
+ {threads.map((thread) => (
44
+ <li
45
+ className={`thread-item ${
46
+ thread.id === activeThreadId ? "active" : ""
47
+ }`}
48
+ key={thread.id}
49
+ >
50
+ <button
51
+ className="thread-open"
52
+ onClick={() => onSelect(thread.id)}
53
+ type="button"
54
+ >
55
+ <span className="thread-title">{thread.title}</span>
56
+ <span className="thread-time">
57
+ {formatTime(thread.updatedAt)}
58
+ </span>
59
+ </button>
60
+ <button
61
+ aria-label="Delete conversation"
62
+ className="thread-delete"
63
+ onClick={() => onDelete(thread.id)}
64
+ type="button"
65
+ >
66
+ ×
67
+ </button>
68
+ </li>
69
+ ))}
70
+ </ul>
71
+ </aside>
72
+ );
73
+ }
@@ -0,0 +1,89 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+
5
+ export type ToolCallStatus = "running" | "complete" | "error";
6
+
7
+ export type ToolCallView = {
8
+ id: string;
9
+ name: string;
10
+ args: Record<string, unknown>;
11
+ output?: string;
12
+ status: ToolCallStatus;
13
+ };
14
+
15
+ function stringifyArgs(args: Record<string, unknown>) {
16
+ try {
17
+ return JSON.stringify(args, null, 2);
18
+ } catch {
19
+ return String(args);
20
+ }
21
+ }
22
+
23
+ function statusLabel(status: ToolCallStatus) {
24
+ if (status === "running") return "Running";
25
+ if (status === "error") return "Error";
26
+ return "Done";
27
+ }
28
+
29
+ function ToolIcon() {
30
+ return (
31
+ <svg
32
+ aria-hidden
33
+ fill="none"
34
+ stroke="currentColor"
35
+ strokeLinecap="round"
36
+ strokeLinejoin="round"
37
+ strokeWidth="1.6"
38
+ viewBox="0 0 24 24"
39
+ >
40
+ <path d="M14.7 6.3a4 4 0 0 1-5.4 5.4L4 17v3h3l5.3-5.3a4 4 0 0 1 5.4-5.4l-2.7 2.7-1.4-1.4 2.7-2.7a4 4 0 0 0-1.6.4z" />
41
+ </svg>
42
+ );
43
+ }
44
+
45
+ /**
46
+ * A subtle, collapsible representation of a single tool call: an icon, the
47
+ * tool name, and its status. Expanding reveals the stringified input (args)
48
+ * and output (the tool result).
49
+ */
50
+ export function ToolCall({ call }: { call: ToolCallView }) {
51
+ const [open, setOpen] = useState(false);
52
+
53
+ return (
54
+ <div className={`toolcall status-${call.status}`}>
55
+ <button
56
+ aria-expanded={open}
57
+ className="toolcall-head"
58
+ onClick={() => setOpen((value) => !value)}
59
+ type="button"
60
+ >
61
+ <span className="toolcall-icon">
62
+ <ToolIcon />
63
+ </span>
64
+ <span className="toolcall-name">{call.name}</span>
65
+ <span className={`subagent-status status-${call.status}`}>
66
+ {statusLabel(call.status)}
67
+ </span>
68
+ <span aria-hidden className="toolcall-chevron">
69
+ {open ? "▾" : "▸"}
70
+ </span>
71
+ </button>
72
+
73
+ {open ? (
74
+ <div className="toolcall-body">
75
+ <div className="toolcall-section">
76
+ <span>Input</span>
77
+ <pre>{stringifyArgs(call.args)}</pre>
78
+ </div>
79
+ {call.output != null && call.output !== "" ? (
80
+ <div className="toolcall-section">
81
+ <span>Output</span>
82
+ <pre>{call.output}</pre>
83
+ </div>
84
+ ) : null}
85
+ </div>
86
+ ) : null}
87
+ </div>
88
+ );
89
+ }
@@ -0,0 +1,18 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
@@ -0,0 +1,95 @@
1
+ import "server-only";
2
+
3
+ import { MemorySaver } from "@langchain/langgraph";
4
+ import { ChatOpenAI } from "@langchain/openai";
5
+ import { createDeepAgent } from "deepagents";
6
+
7
+ import { stripReasoningReplay } from "./middleware";
8
+ import { calculator, searchWeb } from "./tools";
9
+
10
+ /**
11
+ * A "deep agent" coordinator with two subagents.
12
+ *
13
+ * `createDeepAgent` gives the coordinator a built-in `task` tool that it uses
14
+ * to delegate work to named subagents. Each delegated `task` call runs as a
15
+ * namespaced sub-run, so the Agent Streaming Protocol relays its `messages`
16
+ * and `tools` events under a subagent namespace. The `@langchain/react` SDK
17
+ * surfaces those as `stream.subagents`, which the UI renders in dedicated
18
+ * cards.
19
+ *
20
+ * A concrete `ChatOpenAI` instance is passed as the model (instead of a model
21
+ * string) so `langchain`'s string-based `initChatModel` lookup does not have to
22
+ * resolve `@langchain/openai` under the package manager's layout.
23
+ *
24
+ * Reasoning summaries are enabled on the coordinator: over the Responses API,
25
+ * OpenAI returns reasoning *summaries* (not raw chain-of-thought) as
26
+ * `{ type: "reasoning" }` standard content blocks. These stream through the
27
+ * `messages` channel and the UI renders them in a collapsible "Thinking"
28
+ * section.
29
+ *
30
+ * The tool-using subagents deliberately use a plain (non-Responses) model. The
31
+ * Responses API replays prior reasoning items by id on each tool-loop step, and
32
+ * deep-agent subagent history can surface those items with empty ids
33
+ * (`400 Invalid 'input[..].id': ''`). Keeping subagents on standard
34
+ * chat-completions tool calling avoids that, while the orchestrator — which
35
+ * only delegates via the `task` tool — keeps its reasoning summaries.
36
+ *
37
+ * The agent is compiled with an in-memory `MemorySaver` checkpointer so the
38
+ * backend can persist and rehydrate per-thread conversation state. Swap this
39
+ * for a durable checkpointer (Postgres, SQLite, …) before deploying — see the
40
+ * note in `lib/server/registry.ts`.
41
+ */
42
+ const coordinatorModel = new ChatOpenAI({
43
+ model: "gpt-5.4-mini",
44
+ reasoning: { effort: "low", summary: "auto" },
45
+ });
46
+
47
+ const subagentModel = new ChatOpenAI({ model: "gpt-5.4-mini" });
48
+
49
+ /**
50
+ * In-memory checkpointer — the single source of truth for threads.
51
+ *
52
+ * Exported so the server can enumerate threads (via `checkpointer.storage`) and
53
+ * delete them (`checkpointer.deleteThread`). It is process-local and volatile:
54
+ * restarting the server clears every thread.
55
+ */
56
+ export const checkpointer = new MemorySaver();
57
+
58
+ export const agent = createDeepAgent({
59
+ model: coordinatorModel,
60
+ middleware: [stripReasoningReplay],
61
+ checkpointer,
62
+ subagents: [
63
+ {
64
+ name: "researcher",
65
+ description:
66
+ "Researches a topic using the search_web tool and reports concise findings.",
67
+ tools: [searchWeb],
68
+ model: subagentModel,
69
+ systemPrompt:
70
+ "You are the researcher subagent. Use the search_web tool to look up " +
71
+ "the requested topic, then summarize the findings in two or three " +
72
+ "sentences. Always call search_web at least once before answering.",
73
+ },
74
+ {
75
+ name: "math-whiz",
76
+ description:
77
+ "Performs calculations using the calculator tool and explains the result.",
78
+ tools: [calculator],
79
+ model: subagentModel,
80
+ systemPrompt:
81
+ "You are the math-whiz subagent. Use the calculator tool to evaluate " +
82
+ "the requested expression, then state the result clearly. Always call " +
83
+ "the calculator tool before answering.",
84
+ },
85
+ ],
86
+ systemPrompt:
87
+ "You are a helpful coordinator. When a request involves looking something " +
88
+ "up, delegate it to the `researcher` subagent. When it involves math, " +
89
+ "delegate it to the `math-whiz` subagent. You may run both subagents for a " +
90
+ "single request. After the subagents respond, combine their results into a " +
91
+ "short, clearly labeled final answer.",
92
+ });
93
+
94
+ /** The compiled agent type, re-exported for `useStreamContext<typeof agent>()`. */
95
+ export type Agent = typeof agent;
@@ -0,0 +1,40 @@
1
+ import "server-only";
2
+
3
+ import { AIMessage, type BaseMessage } from "@langchain/core/messages";
4
+ import { createMiddleware } from "langchain";
5
+
6
+ /**
7
+ * Rebuild prior assistant messages so the Responses API doesn't replay stale
8
+ * item ids.
9
+ *
10
+ * Over the Responses API, `@langchain/openai` replays an assistant turn either
11
+ * from `response_metadata.output` (the raw response items) or by reconstructing
12
+ * items from `additional_kwargs` — both of which carry ids (reasoning items,
13
+ * function-call items). After a round-trip through the checkpointer those ids
14
+ * can come back empty, and OpenAI rejects the next call with
15
+ * `400 Invalid 'input[..].id': ''`.
16
+ *
17
+ * We rebuild each prior assistant message from just its `content` and
18
+ * `tool_calls`. Tool calls keep their `call_id` (the valid pairing key), the
19
+ * converter emits clean items with no stale ids, and reasoning items are
20
+ * dropped from the model *input*. State is untouched, so the UI still renders
21
+ * each turn's reasoning; the model simply produces fresh reasoning per turn and
22
+ * never receives the old items back.
23
+ */
24
+ function sanitizeForReplay(message: BaseMessage): BaseMessage {
25
+ if (!AIMessage.isInstance(message)) return message;
26
+
27
+ return new AIMessage({
28
+ id: message.id,
29
+ content: message.content,
30
+ tool_calls: message.tool_calls,
31
+ invalid_tool_calls: message.invalid_tool_calls,
32
+ usage_metadata: message.usage_metadata,
33
+ });
34
+ }
35
+
36
+ export const stripReasoningReplay = createMiddleware({
37
+ name: "StripReasoningReplay",
38
+ wrapModelCall: async (request, handler) =>
39
+ handler({ ...request, messages: request.messages.map(sanitizeForReplay) }),
40
+ });
@@ -0,0 +1,66 @@
1
+ import "server-only";
2
+
3
+ import { tool } from "langchain";
4
+ import { z } from "zod";
5
+
6
+ /**
7
+ * Mock tools used to demonstrate message and tool-call streaming.
8
+ *
9
+ * Both tools are intentionally fake so the example runs offline. What matters
10
+ * is that the agent (and its subagents) emit real tool-call deltas on the
11
+ * `messages` channel and tool results as `ToolMessage`s, which the UI renders.
12
+ */
13
+
14
+ export const searchWeb = tool(
15
+ async ({ query }) => {
16
+ await new Promise((resolve) => setTimeout(resolve, 300));
17
+ return JSON.stringify({
18
+ results: [
19
+ {
20
+ title: `Result for: ${query}`,
21
+ snippet:
22
+ "LangGraph streaming sends token deltas on the messages channel " +
23
+ "and tool lifecycle events on the tools channel.",
24
+ },
25
+ ],
26
+ });
27
+ },
28
+ {
29
+ name: "search_web",
30
+ description: "Search the web for information about a topic.",
31
+ schema: z.object({ query: z.string().describe("Search query.") }),
32
+ },
33
+ );
34
+
35
+ /** Demo-only arithmetic evaluator restricted to numbers and basic operators. */
36
+ function evaluateExpression(expression: string): number {
37
+ if (!/^[\d+\-*/().\s]+$/.test(expression)) {
38
+ throw new Error("Only basic arithmetic is supported.");
39
+ }
40
+ const compute = new Function(
41
+ `"use strict"; return (${expression});`,
42
+ ) as () => unknown;
43
+ const result = compute();
44
+ if (typeof result !== "number" || !Number.isFinite(result)) {
45
+ throw new Error("Expression did not evaluate to a finite number.");
46
+ }
47
+ return result;
48
+ }
49
+
50
+ export const calculator = tool(
51
+ async ({ expression }) => {
52
+ await new Promise((resolve) => setTimeout(resolve, 100));
53
+ try {
54
+ return String(evaluateExpression(expression));
55
+ } catch (error) {
56
+ return `Error evaluating: ${expression} (${String(error)})`;
57
+ }
58
+ },
59
+ {
60
+ name: "calculator",
61
+ description: "Evaluate a math expression.",
62
+ schema: z.object({
63
+ expression: z.string().describe("Math expression to evaluate."),
64
+ }),
65
+ },
66
+ );
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Browser-side thread helpers.
3
+ *
4
+ * The server (the agent's in-memory checkpointer) is the single source of truth
5
+ * for threads. There is no client-side cache: the sidebar is always fetched
6
+ * from the API, and restarting the server clears every thread.
7
+ */
8
+
9
+ import { Client } from "@langchain/langgraph-sdk/client";
10
+
11
+ /** LangGraph SDK base URL. Route handlers live under `/api/threads/...`. */
12
+ export function getApiUrl(): string {
13
+ return `${window.location.origin}/api`;
14
+ }
15
+
16
+ /** Summary of a thread for the history sidebar (mirrors the server type). */
17
+ export type ThreadSummary = {
18
+ id: string;
19
+ title: string;
20
+ updatedAt: string | null;
21
+ };
22
+
23
+ /** Fetch every thread from the server, newest first. */
24
+ export async function fetchThreads(): Promise<ThreadSummary[]> {
25
+ const response = await fetch(`${getApiUrl()}/threads`, { cache: "no-store" });
26
+ if (!response.ok) return [];
27
+ return (await response.json()) as ThreadSummary[];
28
+ }
29
+
30
+ /**
31
+ * Create the thread row server-side so hydration does not 404.
32
+ *
33
+ * Calls `GET /threads/:id/state` and, on 404, bootstraps with
34
+ * `POST /threads/:id/state` and empty `messages`.
35
+ */
36
+ async function ensureThreadExists(threadId: string) {
37
+ const client = new Client({ apiUrl: getApiUrl() });
38
+ try {
39
+ await client.threads.getState(threadId);
40
+ } catch (error) {
41
+ const status = (error as { status?: number })?.status;
42
+ if (status !== 404) throw error;
43
+ await client.threads.updateState(threadId, { values: { messages: [] } });
44
+ }
45
+ }
46
+
47
+ /** Mint a new thread and bootstrap its checkpoint on the server. */
48
+ export async function createThread(): Promise<string> {
49
+ const id = crypto.randomUUID();
50
+ await ensureThreadExists(id);
51
+ return id;
52
+ }
53
+
54
+ /** Delete a thread (session + checkpointed state) on the server. */
55
+ export async function deleteThread(threadId: string): Promise<void> {
56
+ await fetch(`${getApiUrl()}/threads/${threadId}`, { method: "DELETE" });
57
+ }
@@ -0,0 +1,57 @@
1
+ import "server-only";
2
+
3
+ import { agent, checkpointer } from "@/lib/agent";
4
+ import { LocalThreadSession } from "./session";
5
+
6
+ /**
7
+ * Process-local registry for the agent and its per-thread sessions.
8
+ *
9
+ * Next.js route handlers are stateless per request, and the dev server
10
+ * re-evaluates modules on hot reload. Stashing the agent and the session map on
11
+ * `globalThis` keeps a single agent instance (and therefore a single
12
+ * `MemorySaver` checkpointer) alive across requests and reloads, so a thread's
13
+ * conversation state survives between the `/state`, `/commands`, and `/stream`
14
+ * calls that make up one turn.
15
+ *
16
+ * NOTE: This is in-memory and process-local. A serverless/multi-instance
17
+ * deployment needs a durable checkpointer (Postgres, SQLite, …) and a shared
18
+ * session/replay store. The wiring here stays the same; only the checkpointer
19
+ * in `lib/agent/index.ts` and this store change.
20
+ */
21
+ type Registry = {
22
+ sessions: Map<string, LocalThreadSession>;
23
+ };
24
+
25
+ const globalForRegistry = globalThis as unknown as {
26
+ __agentRegistry?: Registry;
27
+ };
28
+
29
+ const registry: Registry = (globalForRegistry.__agentRegistry ??= {
30
+ sessions: new Map(),
31
+ });
32
+
33
+ /** The shared, compiled agent (and its checkpointer). */
34
+ export function getAgent() {
35
+ return agent;
36
+ }
37
+
38
+ /** The shared checkpointer — the single source of truth for threads. */
39
+ export function getCheckpointer() {
40
+ return checkpointer;
41
+ }
42
+
43
+ /** Get or create the process-local session for a thread. */
44
+ export function getSession(threadId: string): LocalThreadSession {
45
+ let session = registry.sessions.get(threadId);
46
+ if (session == null) {
47
+ session = new LocalThreadSession(agent, threadId);
48
+ registry.sessions.set(threadId, session);
49
+ }
50
+ return session;
51
+ }
52
+
53
+ /** Delete a thread: remove its session and its checkpointed state. */
54
+ export async function deleteThread(threadId: string): Promise<void> {
55
+ registry.sessions.delete(threadId);
56
+ await checkpointer.deleteThread(threadId);
57
+ }
@@ -0,0 +1,32 @@
1
+ import "server-only";
2
+
3
+ import { BaseMessage } from "@langchain/core/messages";
4
+
5
+ export function isRecord(value: unknown): value is Record<string, unknown> {
6
+ return typeof value === "object" && value !== null;
7
+ }
8
+
9
+ /**
10
+ * Recursively replace LangChain message instances with plain protocol dicts.
11
+ *
12
+ * Uses {@link BaseMessage.isInstance} and {@link BaseMessage.toDict} from
13
+ * `@langchain/core/messages` — the canonical LangChain serialization primitive.
14
+ * Message instances surface in `values`/`updates` stream data and in the
15
+ * checkpointer state snapshot returned by the thread-state routes, often nested
16
+ * under `messages`.
17
+ */
18
+ export function sanitizeForJson(value: unknown): unknown {
19
+ if (BaseMessage.isInstance(value)) {
20
+ const { type, data } = value.toDict();
21
+ return sanitizeForJson({ ...data, type });
22
+ }
23
+ if (Array.isArray(value)) return value.map(sanitizeForJson);
24
+ if (isRecord(value)) {
25
+ const result: Record<string, unknown> = {};
26
+ for (const [key, item] of Object.entries(value)) {
27
+ result[key] = sanitizeForJson(item);
28
+ }
29
+ return result;
30
+ }
31
+ return value;
32
+ }